Devo utilizzare: un evento più abbonati o più eventi più abbonati

0

Sto scrivendo un'applicazione di acquisizione dati. Mi chiedo se dovrei utilizzare un evento più abbonati o più eventi più abbonati. Sono preoccupato per le prestazioni. Inoltre, potrei migliorare le prestazioni scaricando gli abbonati su un thread diverso?

Ecco un esempio di ciò che intendo per più eventi Più abbonati.

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());
        }
    }
}

Ecco un esempio di cosa intendo per un evento più abbonati

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
    }
}
    
posta TheColonel26 09.05.2015 - 21:26
fonte

1 risposta

3

Se ottieni questi articoli in lotti, è ovvio che abbia senso utilizzare gli eventi che notificano le modifiche una volta per ogni 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));
    }
}

Tenendo conto del fatto che si opera su raccolte, è anche logico considerare lo standard L'interfaccia INotifyCollectionChanged con il suo evento CollectionChanged , a seconda che si adatti alla logica del dominio o che un evento con nome più appropriato si adatti meglio.

Ma è anche importante pensare a due cose:

  1. Chi è (o dovrebbe essere) responsabile dell'aggiunta di articoli e della notifica? È tutto oggetto di canali, o è un singolo canale che è responsabile? Tutti i canali ottengono tutte le celle o ogni singolo canale riceve una cella specifica?
  2. Facilità d'uso. Il secondo esempio di Handle_EventChannelUpdated utilizza la ricerca sugli articoli per ottenere l'elemento richiesto in base a una condizione. Se non si tratta solo di un esempio, ma di uno scenario di utilizzo abituale, allora è ovviamente qualcosa che deve essere riconsiderato. Cercare ogni volta un singolo oggetto, che può essere ottenuto direttamente, non è la migliore architettura.

Se non sei sicuro di ciò che è meglio da questi due punti di vista, allora puoi semplicemente provare a soddisfare entrambi gli scenari: utilizza gli eventi sia sull'aggregatore globale AllChannels che sui singoli canali. Prova a non gestire lo stesso elemento due volte (forse, usa un po 'di Handled di flag in EventArgs se è possibile).

Offloading di abbonati a thread diversi

Puoi farlo. Il modo più semplice e meno incline agli errori è utilizzare TPL con Parallel.ForEach sui delegati che ottieni da Delegate.GetInvocationList chiamata:

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);
}

Assicurati che i tuoi gestori non siano (o non li facciano così) dipendenti da essere cresciuti sullo stesso thread (come i gestori che modificano l'interfaccia utente).

È anche una possibile idea riconsiderare le responsabilità una volta di più: rendere i gestori responsabili delle operazioni di scarico. Quindi gli stessi gestori scaricano lavoro ad alte prestazioni su altri thread. Ma dovrai stare molto attento, perché probabilmente violerà l'invariante che dopo aver generato l'evento tutte le modifiche sono già state gestite (perché alcuni thread potrebbero continuare a elaborarli anche dopo i ritorni di OnEvent).

P.S.: Anche se non è direttamente correlato alla tua domanda, vorrei aggiungere alcuni punti:

  1. Cerca di non creare le tue nuove classi di delegati specifiche. Mentre possono aggiungere un certo grado di sicurezza del tipo, nella maggior parte dei casi è meglio utilizzare alcune delle classi di delegati generici standard come EventHandler o Action<...> con Func<...> .
  2. Non voglio sembrare qualcuno che ti obbliga a utilizzare una convenzione di denominazione preferita, ma è comunque una buona idea esaminare Norme di denominazione C # su MSDN . La maggior parte delle lingue ha un modo più o meno diffuso, o persino standardizzato di nominare. E tali convenzioni (quando ci si abitua a loro) possono facilitare la lettura e la condivisione dei programmi.
risposta data 11.05.2015 - 09:44
fonte

Leggi altre domande sui tag