sql >> Database >  >> RDS >> Database

Evenementen en discussies in .NET

Ik wil je meteen vertellen dat dit artikel niet in het bijzonder gaat over threads, maar over gebeurtenissen in de context van threads in .NET. Ik zal dus niet proberen threads correct te ordenen (met alle blokken, callbacks, annuleren, enz.). Er zijn veel artikelen over dit onderwerp.

Alle voorbeelden zijn geschreven in C# voor de frameworkversie 4.0 (in 4.6 is alles iets eenvoudiger, maar toch zijn er veel projecten in 4.0). Ik zal ook proberen vast te houden aan C# versie 5.0.

Ten eerste zou ik willen opmerken dat er afgevaardigden zijn voor het .Net-evenementsysteem dat ik ten zeerste aanbeveel om te gebruiken in plaats van iets nieuws uit te vinden. Ik werd bijvoorbeeld vaak geconfronteerd met de volgende 2 methoden voor het organiseren van evenementen.

Eerste methode:

 class WrongRaiser
    {
        public event Action<object> MyEvent;
        public event Action MyEvent2;
    }

Ik zou aanraden om deze methode zorgvuldig te gebruiken. Als je het niet universeel maakt, kun je uiteindelijk meer code schrijven dan verwacht. Als zodanig zal het geen nauwkeurigere structuur geven in vergelijking met de onderstaande methoden.

Uit mijn ervaring kan ik vertellen dat ik het gebruikte toen ik met evenementen begon te werken en mezelf daardoor voor de gek hield. Nu zou ik het nooit voor elkaar krijgen.

Tweede methode:

    class WrongRaiser
    {
        public event MyDelegate MyEvent;
    }

    class MyEventArgs
    {
        public object SomeProperty { get; set; }
    }

    delegate void MyDelegate(object sender, MyEventArgs e);

Deze methode is redelijk geldig, maar is goed voor specifieke gevallen waarin de onderstaande methode om een ​​of andere reden niet werkt. Anders krijg je misschien veel eentonig werk.

En laten we nu eens kijken naar wat er al is gemaakt voor de evenementen.

Universele methode:

    class Raiser
    {
        public event EventHandler<MyEventArgs> MyEvent;
    }

    class MyEventArgs : EventArgs
    {
        public object SomeProperty { get; set; }
    }

Zoals je kunt zien, gebruiken we hier de universele EventHandler-klasse. Dat wil zeggen, het is niet nodig om uw eigen handler te definiëren.

De andere voorbeelden zijn voorzien van de universele methode.

Laten we eens kijken naar het eenvoudigste voorbeeld van de gebeurtenissengenerator.

    class EventRaiser
    {
        int _counter;

        public event EventHandler<EventRaiserCounterChangedEventArgs> CounterChanged;

        public int Counter
        {
            get
            {
                return _counter;
            }

            set
            {
                if (_counter != value)
                {
                    var old = _counter;
                    _counter = value;
                    OnCounterChanged(old, value);
                }
            }
        }

        public void DoWork()
        {
            new Thread(new ThreadStart(() =>
            {
                for (var i = 0; i < 10; i++)
                    Counter = i;
            })).Start();
        }

        void OnCounterChanged(int oldValue, int newValue)
        {
            if (CounterChanged != null)
                CounterChanged.Invoke(this, new EventRaiserCounterChangedEventArgs(oldValue, newValue));
        }
    }

    class EventRaiserCounterChangedEventArgs : EventArgs
    {
        public int NewValue { get; set; }
        public int OldValue { get; set; }
        public EventRaiserCounterChangedEventArgs(int oldValue, int newValue)
        {
            NewValue = newValue;
            OldValue = oldValue;
        }
    }

Hier hebben we een klasse met de eigenschap Counter die kan worden gewijzigd van 0 in 10. Daarbij wordt de logica die Counter verandert verwerkt in een aparte thread.

En hier is ons startpunt:

    class Program
    {
        static void Main(string[] args)
        {
            var raiser = new EventRaiser();
            raiser.CounterChanged += Raiser_CounterChanged;
            raiser.DoWork();
            Console.ReadLine();
        }

        static void Raiser_CounterChanged(object sender, EventRaiserCounterChangedEventArgs e)
        {
            Console.WriteLine(string.Format("OldValue: {0}; NewValue: {1}", e.OldValue, e.NewValue));
        }
    }

Dat wil zeggen dat we een instantie van onze generator maken, ons abonneren op de tellerwijziging en, in de gebeurtenishandler, waarden uitvoeren naar de console.

Dit is wat we als resultaat krijgen:

Tot nu toe, zo goed. Maar laten we eens nadenken, in welke thread wordt de event handler uitgevoerd?

De meeste van mijn collega's beantwoordden deze vraag "Over het algemeen één". Het betekende dat geen van hen niet begreep hoe de afgevaardigden zijn geregeld. Ik zal proberen het uit te leggen.

De klasse Delegate bevat informatie over een methode.

Er is ook zijn afstammeling, MulticastDelegate, die meer dan één element heeft.

Dus wanneer u zich abonneert op een gebeurtenis, wordt een exemplaar van de MulticastDelegate-afstammeling gemaakt. Elke volgende abonnee voegt een nieuwe methode (event-handler) toe aan de reeds aangemaakte instantie van MulticastDelegate.

Wanneer u de Invoke-methode aanroept, worden de handlers van alle abonnees één voor één aangeroepen voor uw evenement. Bovendien weet de thread waarin u deze handlers aanroept niets over de thread waarin ze zijn gespecificeerd en kan dienovereenkomstig niets in die thread invoegen.

Over het algemeen worden de gebeurtenishandlers in het bovenstaande voorbeeld uitgevoerd in de thread die is gegenereerd in de DoWork()-methode. Dat wil zeggen dat tijdens het genereren van gebeurtenissen de thread die het op een dergelijke manier heeft gegenereerd, wacht op uitvoering van alle handlers. Ik zal je dit laten zien zonder ID-threads in te trekken. Hiervoor heb ik enkele coderegels in het bovenstaande voorbeeld gewijzigd.

Bewijs dat alle handlers in het bovenstaande voorbeeld worden uitgevoerd in de thread die de gebeurtenis heeft aangeroepen

Methode waarbij gebeurtenis wordt gegenereerd

        void OnCounterChanged(int oldValue, int newValue)
        {
            if (CounterChanged != null)
            {
                CounterChanged.Invoke(this, new EventRaiserCounterChangedEventArgs(oldValue, newValue));
                Console.WriteLine(string.Format("Event Raiser: old = {0}, new = {1}", oldValue, newValue));
            }
                
        }

Behandelaar

        static void Raiser_CounterChanged(object sender, EventRaiserCounterChangedEventArgs e)
        {
            Console.WriteLine(string.Format("OldValue: {0}; NewValue: {1}", e.OldValue, e.NewValue));
            Thread.Sleep(500);
        }

In de handler sturen we de huidige thread een halve seconde naar de slaapstand. Als handlers in de hoofdthread werkten, zou deze tijd genoeg zijn voor een thread die in DoWork() is gegenereerd om zijn taak te voltooien en de resultaten uit te voeren.

Dit is echter wat we echt zien:

Ik weet niet wie en hoe moet omgaan met de gebeurtenissen die zijn gegenereerd door de klas die ik heb geschreven, maar ik wil niet echt dat deze handlers het werk van mijn klas vertragen. Daarom zal ik de BeginInvoke-methode gebruiken in plaats van Invoke. BeginInvoke genereert een nieuwe thread.

Opmerking:Beide methoden Invoke en BeginInvoke zijn geen leden van de klassen Delegate of MulticastDelegate. Zij zijn de leden van de gegenereerde klasse (of de hierboven beschreven universele klasse).

Als we nu de methode wijzigen waarmee de gebeurtenis wordt gegenereerd, krijgen we het volgende:

Generatie van evenementen met meerdere threads:

        void OnCounterChanged(int oldValue, int newValue)
        {
            if (CounterChanged != null)
            {
                var delegates = CounterChanged.GetInvocationList();
                for (var i = 0; i < delegates.Length; i++)
                    ((EventHandler<EventRaiserCounterChangedEventArgs>)delegates[i]).BeginInvoke(this, new EventRaiserCounterChangedEventArgs(oldValue, newValue), null, null);
                Console.WriteLine(string.Format("Event Raiser: old = {0}, new = {1}", oldValue, newValue));
            }
                
        }

De laatste twee parameters zijn gelijk aan null. De eerste is een callback, de tweede is een bepaalde parameter. Ik gebruik geen callback in dit voorbeeld, omdat het voorbeeld intermediair is. Het kan nuttig zijn voor feedback. Het kan bijvoorbeeld de klasse die de gebeurtenis genereert helpen om te bepalen of een gebeurtenis is afgehandeld en/of dat het nodig is om resultaten van deze afhandeling te krijgen. Het kan ook bronnen vrijmaken die verband houden met asynchrone werking.

Als we het programma uitvoeren, krijgen we het volgende resultaat.

Ik denk dat het vrij duidelijk is dat event-handlers nu in aparte threads worden uitgevoerd, d.w.z. de event-generator maakt het niet uit wie, hoe en hoe lang zijn events afhandelt.

En hier rijst de vraag:hoe zit het met sequentiële afhandeling? We hebben tenslotte Counter. Wat als het een seriële verandering van staten zou zijn? Maar ik zal deze vraag niet beantwoorden, het is geen onderwerp van dit artikel. Ik kan alleen maar zeggen dat er verschillende manieren zijn.

En nog een laatste toevoeging. Om niet steeds dezelfde acties te herhalen, raad ik aan om een ​​aparte klas voor ze te maken.

Een klasse voor het genereren van asynchrone gebeurtenissen

    static class AsyncEventsHelper
    {
        public static void RaiseEventAsync<T>(EventHandler<T> h, object sender, T e) where T : EventArgs
        {
            if (h != null)
            {
                var delegates = h.GetInvocationList();
                for (var i = 0; i < delegates.Length; i++)
                    ((EventHandler<T>)delegates[i]).BeginInvoke(sender, e, h.EndInvoke, null);
            }
        }
    }

In dit geval gebruiken we terugbellen. Het wordt uitgevoerd in dezelfde thread als de handler. Dat wil zeggen, nadat de handlermethode is voltooid, roept de gedelegeerde h.EndInvoke next aan.

Hier is hoe het moet worden gebruikt

        void OnCounterChanged(int oldValue, int newValue)
        {
            AsyncEventsHelper.RaiseEventAsync(CounterChanged, this, new EventRaiserCounterChangedEventArgs(oldValue, newValue)); 
        }

Ik denk dat het nu duidelijk is waarom de universele methode nodig was. Als we gebeurtenissen beschrijven met methode 2, werkt deze truc niet. Anders moet u zelf universaliteit creëren voor uw afgevaardigden.

Opmerking :Voor echte projecten raad ik aan om de evenementenarchitectuur te wijzigen in de context van threads. De beschreven voorbeelden kunnen schade toebrengen aan het applicatiewerk met threads en zijn alleen bedoeld voor informatieve doeleinden.

Conclusie

Hope, ik heb kunnen beschrijven hoe gebeurtenissen werken en waar handlers werken. In het volgende artikel ben ik van plan diep in te gaan op het verkrijgen van resultaten van de gebeurtenisafhandeling wanneer een asynchrone oproep wordt gedaan.

Ik kijk uit naar uw opmerkingen en suggesties.


  1. Rethink Flask - Een eenvoudige takenlijst mogelijk gemaakt door Flask en RethinkDB

  2. Probleem met invoegquery in Sqlite? (variabele invoegen)

  3. FOUT:er is geen unieke beperking die overeenkomt met de gegeven sleutels voor de tabelbalk waarnaar wordt verwezen

  4. MySQL JOIN met LIMIT 1 op samengevoegde tafel