Cum să blochezi până când un eveniment este concediat în c# (Programare, C#, Evenimente)

Arlen Beiler a intrebat.

După ce am pus această întrebare, mă întreb dacă este posibil să aștept ca un eveniment să fie declanșat, apoi să obțin datele evenimentului și să returnez o parte din ele. Cam așa ceva:

private event MyEventHandler event;
public string ReadLine(){ return event.waitForValue().Message; }
...
event("My String");
...elsewhere...
var resp = ReadLine();

Vă rog să vă asigurați că orice soluție pe care o oferiți returnează valoarea direct, în loc să o obțineți din altceva. Întreb dacă metoda de mai sus este disponibilă într-un fel sau altul. Știu despre Auto/ManuelResetEvent, dar nu știu dacă acestea returnează valoarea direct, așa cum am făcut eu mai sus.

Actualizare: Am declarat un eveniment folosind MyEventHandler (care conține un element Message câmp). Am o metodă în alt topic numită ReadLine care așteaptă ca evenimentul să se declanșeze. Atunci când evenimentul se declanșează, metoda WaitForValue (parte a scenei de tratare a evenimentelor) returnează arginții evenimentului, care conțin mesajul. Mesajul este apoi returnat de ReadLine către ceea ce a fost apelat.

Răspunsul acceptat la întrebarea pe care am pus-o a fost ceea ce am făcut eu, dar nu mi se pare corect. Aproape că se simte ca și cum s-ar putea întâmpla ceva cu datele între declanșarea ManuelResetEvent și programul care recuperează datele și le returnează.

Actualizare: Principala problemă cu Auto/ManualResetEvent este că este prea vulnerabilă. Un fir de execuție ar putea aștepta evenimentul și apoi să nu lase suficient timp pentru ca altcineva să îl primească înainte de a-l schimba în altceva. Există o modalitate de a folosi încuietori sau altceva? Poate folosind declarații get și set.

Comentarii

  • Ce ziceți de o buclă while? while (someGlobalvar); cahnge someGlobalvar într-o funcție pe care o atribuiți evenimentului. –  > Por elyashiv.
  • este o așteptare activă și este o soluție foarte proastă –  > Por george.zakaryan.
  • Doar că această întrebare este anterioară celei de mai sus 🙂 –  > Por Arlen Beiler.
  • @Vahid, Cu încă 7 ani de experiență în programare de când am pus această întrebare, aș spune că, dacă trebuie să faci asta, probabil că o faci greșit. Este mai bine să specificați un callback. Dacă lucrați cu sisteme vechi care nu permit altceva, cum ar fi COM, atunci puneți codul să aștepte un ManualResetEvent și setați un al doilea pentru a proteja scrierea până când toate firele au terminat de citit, cred. –  > Por Arlen Beiler.
  • De asemenea, vă recomand să consultați reactivex.io. –  > Por Arlen Beiler.
4 răspunsuri
Vinoth

Puteți utiliza ManualResetEvent. Reinițializați evenimentul înainte de a lansa firul secundar și apoi folosiți funcția WaitOne() pentru a bloca firul curent. Apoi, puteți face ca firul secundar să seteze ManualResetEvent, ceea ce ar face ca firul principal să continue. Ceva de genul acesta:

ManualResetEvent oSignalEvent = new ManualResetEvent(false);

void SecondThread(){
    //DoStuff
    oSignalEvent.Set();
}

void Main(){
    //DoStuff
    //Call second thread
    System.Threading.Thread oSecondThread = new System.Threading.Thread(SecondThread);
    oSecondThread.Start();

    oSignalEvent.WaitOne(); //This thread will block here until the reset event is sent.
    oSignalEvent.Reset();
    //Do more stuff
}

Comentarii

  • Acest lucru nu returnează direct valoarea. Am încercat deja acest lucru în cealaltă întrebare. Nu există nimic asemănător cu ceea ce întreb eu? –  > Por Arlen Beiler.
Adam

Dacă metoda curentă este asincronă, atunci puteți utiliza TaskCompletionSource. Creați un câmp pe care gestionarul de evenimente și metoda curentă îl pot accesa.

    TaskCompletionSource<bool> tcs = null;

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        tcs = new TaskCompletionSource<bool>();
        await tcs.Task;
        WelcomeTitle.Text = "Finished work";
    }

    private void Button_Click2(object sender, RoutedEventArgs e)
    {
        tcs?.TrySetResult(true);
    }

Acest exemplu utilizează un formular care are un bloc de text numit WelcomeTitle și două butoane. Atunci când se face clic pe primul buton, se declanșează evenimentul click, dar se oprește la linia de așteptare. Atunci când se face clic pe al doilea buton, sarcina este finalizată și textul WelcomeTitle este actualizat. Dacă doriți să se aplice și timeout, modificați

await tcs.Task;

în

await Task.WhenAny(tcs.Task, Task.Delay(25000));
if (tcs.Task.IsCompleted)
    WelcomeTitle.Text = "Task Completed";
else
    WelcomeTitle.Text = "Task Timed Out";

Comentarii

  • Nu aveți nevoie de async pentru a utiliza TaskCompletionSource –  > Por Felix Keil.
  • Este adevărat, dar aveți nevoie de el dacă doriți să așteptați sarcina fără a bloca interfața de utilizator. –  > Por Adam.
  • Poate fi resetat un TaskCompletionSource? –  > Por Kyle Delaney.
  • @KyleDelaney Nu.În exemplul meu, ați înlocui tcs cu o nouă instanță.  > Por Adam.
  • Cum puteți face acest lucru cu un eveniment care nu este un clic pe buton? Există o modalitate de a invoca un eveniment, de a aștepta ca gestionarul să se termine de executat și apoi, la sfârșit, de a apela TrySetResult(true)? –  > Por Muhannad.
mbarthelemy

Un tip de eveniment foarte simplu pe care îl puteți aștepta este evenimentul ManualResetEvent, și chiar mai bine, evenimentul ManualResetEventSlim.

Acestea au un WaitOne() care face exact acest lucru. Puteți aștepta la nesfârșit sau puteți seta un timeout sau un „token de anulare”, care este o modalitate prin care puteți decide să nu mai așteptați evenimentul (dacă doriți să vă anulați activitatea sau dacă aplicația dumneavoastră este rugată să iasă).

Le lansați apelând Set().

Aici este documentul.

Comentarii

  • Dar asta nu returnează niciun fel de valoare. –  > Por Arlen Beiler.
  • Poți să editezi qu și să adaugi o prezentare generală a diferitelor componente și a interacțiunilor lor? Ne va ajuta să vedem mai bine ce încercați să obțineți și, sperăm, să ducă la un design îmbunătățit. –  > Por Michael.
Enigmativity

Dacă sunteți mulțumit să folosiți Microsoft Reactive Extensions, atunci acest lucru poate funcționa foarte bine:

public class Foo
{
    public delegate void MyEventHandler(object source, MessageEventArgs args);
    public event MyEventHandler _event;
    public string ReadLine()
    {
        return Observable
            .FromEventPattern<MyEventHandler, MessageEventArgs>(
                h => this._event += h,
                h => this._event -= h)
            .Select(ep => ep.EventArgs.Message)
            .First();
    }
    public void SendLine(string message)
    {
        _event(this, new MessageEventArgs() { Message = message });
    }
}

public class MessageEventArgs : EventArgs
{
    public string Message;
}

Îl pot folosi în felul următor:

var foo = new Foo();

ThreadPoolScheduler.Instance
    .Schedule(
        TimeSpan.FromSeconds(5.0),
        () => foo.SendLine("Bar!"));

var resp = foo.ReadLine();

Console.WriteLine(resp);

Aveam nevoie să apelez la SendLine pe un alt fir de execuție pentru a evita blocarea, dar acest cod arată că funcționează așa cum era de așteptat.