0

I am writing a data acquisition application. I wondering whether I should use one event multiple subscribers or multiple events multiple subscribers. I am worried about performance. Also could I improve performance by offloading the subscribers to a different thread?

Here is an example of what I mean by multiple events Multiple subscribers.

public delegate void EventChannelUpdated(object sender, EventArgsChannelUpdated e);

public class EventArgsChannelUpdated : EventArgs
{
    public chCell chCell;
}

public delegate void EventLimitExceeded(object sender, EventArgsLimitExceeded args);

public class EventArgsLimitExceeded : EventArgs
{
}

class Example
{
    public List<Channel> AllChannels;
}

public class Saver_allChannels
{
    private Example example;

    public event EventChannelUpdated eventChannelUpdated;

    public void DataAdded()
    {

        //** this part is pseudo code as I have simplified some of the hwObjs structure
        for (int i = 0; i < hwObjs.Count; i++)
        {
            example.ListOfchannels[i].addSamples(hwObjs[i].Reading);
        }
    }
}

public class Channel 
{
    public List<chCell> column { get; set; }

    public string label { get; set; }

    public event EventChannelUpdated eventChannelUpdated;

    public double multipler { get; set; }

    public void addSamples(double unScaledValue)
    {
        chCell temp = new chCell() {channel = this, scaledValue = unScaledValue*multipler};
        column.Add(temp);
        eventChannelUpdated(this,new EventArgsChannelUpdated(){chCell = temp});
    }
}

public class chCell
{
    public double scaledValue { get; set; }

    public Channel channel { get; set; } //** parent Channel List
}

public class Monitor
{
    public double Limit;

    public event EventLimitExceeded eventLimitExceed;

    public void Handle_EventChannelUpdated(object sender, EventArgsChannelUpdated args)
    {
        if (args.chCell.scaledValue > Limit)
        {
            eventLimitExceed(this, new EventArgsLimitExceeded());
        }
    }
}

Here is an example of what I mean by one event multiple subscribers

public delegate void EventChannelUpdated(object sender, EventArgsChannelsUpdated e);

public class EventArgsChannelsUpdated : EventArgs
{
    public List<chCell> RowOfchCells;
}

public delegate void EventLimitExceeded(object sender, EventArgsLimitExceeded args);

public class EventArgsLimitExceeded : EventArgs
{
}

class Example
{
    public List<Channel> ListOfchannels;
}


public class Saver_allChannels
{
    Example example;

    public event EventChannelUpdated eventChannelUpdated;

    public void DataAdded()
    {

        List<chCell> LastRow = new List<chCell>();

        //** this part is pseudo code as I have simplified some of the hwObjs structure
        for (int i = 0; i < hwObjs.Count; i++)
        {               
            example.ListOfchannels[i].addSamples(hwObjs[i].Reading);
            LastRow.Add(new chCell() {channel = example.ListOfchannels[i], Value = hwObjs[i].Reading});
        }

        eventChannelUpdated(this, new EventArgsChannelsUpdated() { RowOfchCells = LastRow });

    }
}

public class Channel 
{
    public List<chCell> column { get; set; }

    public string label { get; set; }

    public double multipler { get; set; }

    public void addSamples(double unScaledValue)
    {
        chCell temp = new chCell() {channel = this, scaledValue = unScaledValue*multipler};
        column.Add(temp);
    }
}

public class chCell
{
    public double scaledValue { get; set; }

    public Channel channel { get; set; } //** parent Channel List
}

public class Monitor
{
    public double Limit;

    public Channel subbedChannel;

    public event EventLimitExceeded eventLimitExceed;

    public void Handle_EventChannelUpdated(object sender, EventArgsChannelsUpdated args)
    {
        double value = (args.RowOfchCells.FirstOrDefault(x => x.channel == subbedChannel)).scaledValue;
        //** do something with value
    }
}
3

If you get these items in batches, then it obviously makes sense to use events that notify about changes once per batch:

public class AllChannelsSaver
{
    public void Add(IEnumerable<ChCell> cells)
    {
        this.m_cells.Add(cells);

        this.OnCellsAdded(cells);
    }

    public event EventHandler<MultipleCellsAddedEventArgs> CellsAdded;

    private void OnCellsAdded(IEnumerable<ChCell> cells)
    {
        var cellsAddedDelegate = this.CellsAdded;

        if (cellsAddedDelegate == null)
            return;

        cellsAddedDelegate(this, new MultipleCellsAddedEventArgs(cells));
    }
}

Taking into account that you operate over collections it even makes sense to consider standard INotifyCollectionChanged interface with its CollectionChanged event, depending on whether it suits the domain logic or a more appropriately named event will fit better.


But it is also important to think about two things:

  1. Who is (or should be) responsible for adding items and notifying? Is it all channels object, or is it an individual Channel who is responsible? Do all channels get all cells, or each individual channel gets specific cell?
  2. Ease of use. Your second example's Handle_EventChannelUpdated uses search over the items to get the required item by some condition. If it is not just some example, but a usual usage scenario, then it is obviously something that needs to be reconsidered. Searching each time for a single item, that can be obtained directly is not the best architecture.

If you are not sure what is better from these two points of view, then you can just try to cater to both scenarios - use events on both the global AllChannels aggregator and the individual channels. Just try not to handle the same item twice (perhaps, use some additional Handled flag in EventArgs if it is possible).


Offloading subscribers to different threads

You can do it. The most simple and less error-prone way is to use TPL with Parallel.ForEach over the delegates you get from Delegate.GetInvocationList call:

public static void Execute(object sender, EventArgs args, Int32 number, Int32 delayMs)
{
    Console.WriteLine("Entering {0}", number);

    Console.WriteLine("Sender = {0}; EventArgs = {1}",
        sender,
        args);

    Thread.Sleep(delayMs);

    Console.WriteLine("Exiting {0}", number);
}

public static void Main()
{
    var eventHandler = new EventHandler((sender, args) =>
        Execute(sender, args, 0, 1000));

    eventHandler += (sender, args) =>
        Execute(sender, args, 1, 1000);

    eventHandler += (sender, args) =>
        Execute(sender, args, 2, 2000);

    var senderArg = typeof(Program);
    var eventArgs = new EventArgs();

    Parallel
        .ForEach(
            eventHandler
                .GetInvocationList()
                .Cast<EventHandler>(),
            (handler) =>
                handler(senderArg, eventArgs));

    Console.WriteLine("All is done. Press any key.");
    Console.ReadKey(false);
}

Just make sure that your handlers are not(or make them so) dependent on being raised on the same thread (like handlers modifying UI).

It is also a possible idea to reconsider responsibilies once more - make handlers responsible for offloading. So the handlers themselves offload long-performing job to other threads. But you will have to be very careful, because it will probably violate the invariant that after raising the event all changes have been already handled(because some thread may still continue to process them even after OnEvent returns).

P.S.: While it is not directly related to your question, I'd like to add some points:

  1. Try not to create your specific new delegate classes. While they may add some degree of type-safety, in most cases it is better to use some of the standard generic delegate classes like EventHandler or Action<...> with Func<...>.
  2. I do not want to look like someone who forces you to use a preferred naming convention, but it is still a good idea for you to look into C# naming guidelines on MSDN. Most languages have some more or less widespread, or even standardized way of naming. And such conventions(when you become accustomed to them) can make reading and sharing of programs much easier.
  • Yes, but I will have listeners for individual channels (either directly or indirectly) and some Channels will have multiple listeners. So if I use one event, each subscriber method will receive everything and then it will have to iterate through or use indexes to pick out what information it needs. Does you advice still hold? – TheColonel26 May 16 '15 at 21:26
  • As for my delegate types what is the advantage to using a generic delegate type? also what specifically is wrong with my naming conventions are you talking about be using prefixes like event, handler, list? – TheColonel26 May 16 '15 at 21:34
  • @TheColonel26 No one knows your system or its requirements better than you, so if you say that you need to handle each channel individually, then event per channel makes more sense - I have already told you about it in the "But it is also important to think about two things:" section. – Eugene Podskal May 17 '15 at 9:48
  • @TheColonel26 You mix Pascal casing with first lower letter Camel casing (like in Java). And you even use it inconsistently for both classes and members. About your event names neither I personally nor MSDN have nothing against, but it is at least unusual. – Eugene Podskal May 17 '15 at 10:01
  • 1
    @TheColonel26 In any case no one forces to use standard conventions if they do not fit your preferences or company's coding standards. It is your own choice, just apply it consistently through your entire codebase. But if you have no real reason to stick with custom convention, then it is obviously better to apply the industry-wide conventions. – Eugene Podskal May 17 '15 at 10:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.