sql >> Database teknologi >  >> RDS >> Database

Begivenheder og tråde i .NET

Jeg vil gerne fortælle dig direkte, at denne artikel ikke vil omhandle tråde i særdeleshed, men begivenheder i forbindelse med tråde i .NET. Så jeg vil ikke prøve at arrangere tråde korrekt (med alle blokeringer, tilbagekald, annullering osv.) Der er mange artikler om dette emne.

Alle eksempler er skrevet i C# til rammeversion 4.0 (i 4.6 er alt lidt nemmere, men alligevel er der mange projekter i 4.0). Jeg vil også prøve at holde mig til C# version 5.0.

For det første vil jeg gerne bemærke, at der er klar delegerede til .Net-begivenhedssystemet, som jeg varmt anbefaler at bruge i stedet for at opfinde noget nyt. For eksempel stod jeg ofte over for følgende 2 metoder til at organisere begivenheder.

Første metode:

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

Jeg vil anbefale at bruge denne metode omhyggeligt. Hvis du ikke universaliserer det, kan du i sidste ende skrive mere kode end forventet. Som sådan vil den ikke sætte en mere præcis struktur, hvis den sammenlignes med metoderne nedenfor.

Ud fra min erfaring kan jeg fortælle, at jeg brugte det, da jeg begyndte at arbejde med begivenheder og derfor gjorde mig selv til grin. Nu ville jeg aldrig få det til at ske.

Anden metode:

    class WrongRaiser
    {
        public event MyDelegate MyEvent;
    }

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

    delegate void MyDelegate(object sender, MyEventArgs e);

Denne metode er ganske gyldig, men den er god til specifikke tilfælde, hvor metoden nedenfor af nogle årsager ikke virker. Ellers kan du få en masse monotont arbejde.

Og lad os nu tage et kig på, hvad der allerede er blevet oprettet til begivenhederne.

Universal metode:

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

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

Som du kan se, bruger vi her den universelle EventHandler-klasse. Det vil sige, at der ikke er behov for at definere din egen handler.

De yderligere eksempler viser den universelle metode.

Lad os tage et kig på det enkleste eksempel på begivenhedsgeneratoren.

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

Her har vi en klasse med egenskaben Counter, der kan ændres fra 0 til 10. Derefter behandles logikken, der ændrer Counter, i en separat tråd.

Og her er vores indgangspunkt:

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

Det vil sige, at vi opretter en instans af vores generator, abonnerer på tællerændringen og, i hændelseshandleren, udsender værdier til konsollen.

Her er, hvad vi får som resultat:

Så langt så godt. Men lad os tænke, i hvilken tråd udføres hændelseshandler?

De fleste af mine kolleger svarede på dette spørgsmål "Generelt et". Det betød, at ingen af ​​dem ikke forstod, hvordan delegerede er indrettet. Jeg vil prøve at forklare det.

Delegate-klassen indeholder information om en metode.

Der er også dens efterkommer, MulticastDelegate, der har mere end ét element.

Så når du abonnerer på en begivenhed, oprettes en forekomst af MulticastDelegate-efterkommeren. Hver næste abonnent tilføjer en ny metode (hændelseshandler) i den allerede oprettede forekomst af MulticastDelegate.

Når du kalder Invoke-metoden, kaldes alle abonnenters behandlere en efter en til din begivenhed. Den tråd, hvori du kalder disse behandlere, ved ikke noget om den tråd, de blev specificeret i, og den kan tilsvarende ikke indsætte noget i den tråd.

Generelt udføres hændelseshandlerne i eksemplet ovenfor i tråden, der er genereret i DoWork()-metoden. Det vil sige, under hændelsesgenerering venter tråden, der genererede den på en sådan måde, på udførelse af alle handlere. Jeg vil vise dig dette uden at trække Id-tråde tilbage. Til dette ændrede jeg nogle få kodelinjer i ovenstående eksempel.

Bevis på, at alle behandlerne i ovenstående eksempel er udført i tråden, der kaldte hændelsen

Metode, hvor begivenheden genereres

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

Behandler

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

I handleren sender vi den aktuelle tråd i dvale i et halvt sekund. Hvis handlere arbejdede i hovedtråden, ville denne gang være nok til, at en tråd genereret i DoWork() kunne afslutte sit job og udlæse resultaterne.

Men her er hvad vi virkelig ser:

Jeg ved ikke, hvem og hvordan der skal håndtere de begivenheder, der er genereret af den klasse, jeg skrev, men jeg ønsker ikke rigtig, at disse behandlere skal bremse arbejdet i min klasse. Derfor vil jeg bruge BeginInvoke-metoden i stedet for Invoke. BeginInvoke genererer en ny tråd.

Bemærk:Begge metoderne Invoke og BeginInvoke er ikke medlemmer af Delegate- eller MulticastDelegate-klasserne. De er medlemmer af den genererede klasse (eller den universelle klasse beskrevet ovenfor).

Nu, hvis vi ændrer metoden, hvorpå hændelsen genereres, får vi følgende:

Generering af flertrådede begivenheder:

        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 sidste to parametre er lig med null. Den første er et tilbagekald, den anden er en bestemt parameter. Jeg bruger ikke tilbagekald i dette eksempel, da eksemplet er mellemliggende. Det kan være nyttigt til feedback. For eksempel kan det hjælpe klassen, der genererer hændelsen, med at afgøre, om en hændelse blev håndteret og/eller om det er nødvendigt for at få resultater af denne håndtering. Det kan også frigøre ressourcer relateret til asynkron drift.

Hvis vi kører programmet, får vi følgende resultat.

Jeg gætter på, at det er helt klart, at nu udføres hændelsesbehandlere i separate tråde, dvs. hændelsesgeneratoren er ligeglad med, hvem, hvordan og hvor længe der vil håndtere sine hændelser.

Og her opstår spørgsmålet:hvad med sekventiel håndtering? Vi har jo tæller. Hvad hvis det ville være en seriel ændring af stater? Men jeg vil ikke besvare dette spørgsmål, det er ikke emnet for denne artikel. Jeg kan kun sige, at der er flere måder.

Og en ting mere. For ikke at gentage de samme handlinger igen og igen, foreslår jeg at oprette en separat klasse til dem.

En klasse til generering af asynkrone hændelser

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

I dette tilfælde bruger vi tilbagekald. Det udføres i samme tråd som handleren. Det vil sige, efter at behandlermetoden er fuldført, kalder den delegerede h.EndInvoke next.

Her er hvordan det skal bruges

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

Jeg gætter på, at det nu er klart, hvorfor den universelle metode var nødvendig. Hvis vi beskriver hændelser med metode 2, virker dette trick ikke. Ellers bliver du nødt til at skabe universalitet for dine delegerede på egen hånd.

Bemærk :For rigtige projekter anbefaler jeg at ændre begivenhedsarkitekturen i forbindelse med tråde. De beskrevne eksempler kan skade påføringsarbejdet med tråde og er kun givet til informative formål.

Konklusion

Hope, jeg formåede at beskrive, hvordan begivenheder fungerer, og hvor handlere arbejder. I den næste artikel planlægger jeg at dykke dybt ned i at få resultater af hændelseshåndteringen, når der foretages et asynkront opkald.

Jeg ser frem til dine kommentarer og forslag.


  1. Hvordan indstilles startværdi og automatisk stigning i MySQL?

  2. Sådan deaktiveres Change Data Capture (CDC) på en database i SQL Server - SQL Server Tutorial

  3. MariaDB ROW_COUNT() Forklaret

  4. Introduktion til OPENJSON med eksempler (SQL-server)