Laden...

Asynchrones "locken" mit while (!task.IsCompleted) await task?

Erstellt von Palladin007 vor 6 Jahren Letzter Beitrag vor 6 Jahren 2.319 Views
Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 6 Jahren
Asynchrones "locken" mit while (!task.IsCompleted) await task?

Guten Tag,

ich schau mir gerade die Kamera-FUnktionen in UWP an und habe dabei in dieser Klasse (Zeile 502 bis 505) ein interessantes Stück Code gefunden.

Zusammengefasst sieht das ungefähr so aus:

private Task _setupTask = Task.CompletedTask;

private async Task SetupAsync()
{
    // Avoid reentrancy: Wait until nobody else is in this function.
    while (!_setupTask.IsCompleted)
    {
        await _setupTask;
    }

    Func<Task> setupAsync = async () =>
    {
        // do async setup functionality
    };
    _setupTask = setupAsync();

    await _setupTask;
}

Mich interessiert die while-Schleife.

Dass man in einer async-Methode nicht locken kann, wenn ich im lock ein await habe, ist mir klar.
Monitor beschwert sich, dass ein Thread, der das Lock-Objekt nicht besitzt, das auch nicht wieder frei geben kann.

Aber ist diese Variante eine "gute" Lösung für das Problem?
Im Prinzip schützt diese Variante nicht davor, dass ein Anderer Thread genau in dem Moment, in dem die Schleife beendet ist, den Setup-Task erstellt bzw. setzt.

Meine Lösung wäre dagegen etwas anders:

private EventWaitHandle _setupAllowedEvent = new EventWaitHandle(true, EventResetMode.AutoReset);

private async Task SetupAsync()
{
    _setupAllowedEvent.WaitOne();

    Func<Task> setupAsync = async () =>
    {
        // do async setup functionality
    };

    await setupAsync();

    _setupAllowedEvent.Set();
}

Die Veriante hab ich im Sample auch eingebaut und ein kurzer Rauch-Test klappt wunderbar.
Wobei ich auf die Schneller auch keine mehrfachen Setup-Aufrufe provoziert habe.

Ich würde ganz gerne verstehen, warum das in dem Microsoft-Sample so - aus meiner Sicht - merkwürdig gelöst wurde.

Beste Grüße

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

D
985 Beiträge seit 2014
vor 6 Jahren

Den Lock brauchst du nicht, denn die Aufrufe erfolgen dort immer vom UI Thread 😉

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 6 Jahren

Hm - stimmt ...

Aber dann bleibt noch die Frage:
Wozu die while-Schleife und auf den Setup-Task warten?

Ist es tatsächlich wahrscheinlich, dass die App so schnell geöffnet, geschlossen und wieder geöffnet wird, dass das Setup ein zweites Mal aufgerufen wird, bevor der erste Durchlauf durch ist?

Und wäre es da nicht trotzdem besser, mit dem EventWaitHandle zu arbeiten? Nicht, weil es technisch besser bzw. sicherer ist, sondern weil der Code dadurch übersichtlicher wird und ich mir eine Klassen-Variable spare. Außerdem hab ich ja keinen spürbaren Nachteil dadurch, oder übersehe ich (noch) etwas?

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

D
985 Beiträge seit 2014
vor 6 Jahren

Mit deiner Variante hast du dir einen schönen DeadLock gebaut


EventWaitHandle _setupAllowedHandle = new EventWaitHandle(true, EventResetMode.AutoReset);

async Task SetupAsync()
{
    _setupAllowedHandle.WaitOne();

    Func<Task> setupAsync = async () =>
    {
        await Task.Delay(2000).ConfigureAwait(false);
    };

    await setupAsync();
    _setupAllowedHandle.Set();
}

private async void button1_Click(object sender, EventArgs e)
{
    await SetupAsync();
}

Einfach innerhalb von 2 Sekunden zweimal auf den Button hauen.

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 6 Jahren

Tatsache ...

... weil der erste Task im UI-Thread weiter laufen möchte, der aber gerade auf den ersten Task wartet, richtig?

Ja, wenn ich schreibe:

await setupAsync().ConfigureAwait(false);

dann geht's.

Darauf muss man erst mal achten 😄

Aber gut, das Problem hab ich bei einer while-Schleife wie im Sample nicht.
Die wartet schön darauf, bis der Erste Aufruf fertig ist und lässt danach erst durch.
Und da alle Tasks - egal wie oft ich den Button prügle - auf dem gleichen Dispatcher ausgeführt werden, brauch ich mir auf keine Gedanken darüber machen, dass die irgendwie durcheinander kommen.

Ok, ich denke, ich hab's kapiert.

Vielen Dank für die Hilfe 😃

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

16.830 Beiträge seit 2008
vor 6 Jahren

Siehe dazu Async/Await - Best Practices in Asynchronous Programming

ConfigureAwait ist ein leider sehr oft vorhandener Pitfall.

Palladin007 Themenstarter:in
2.079 Beiträge seit 2012
vor 6 Jahren

Dann hab ich wohl ein bisschen Lesestoff für's Wochenende ^^

Das ganze Thema ist aber auch echt gefährlich, weil so viel automatisch passiert, was man erst mal wissen muss um überhaupt zu kapieren, was das eigentliche Problem ist.

Aber ihr habt mir beide sehr weiter geholfen und unter Anderem ein Problem, was mich schon ziemlich lange nervt, gelöst 😄

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

D
985 Beiträge seit 2014
vor 6 Jahren

Speziell zum Async-Reentrancy kannst du dir auch diesen Link anschauen, dort gibt es 5 Pattern (u.a. auch einen mit einem SemaphoreSlim, der deinem gedachten EventWaitHandle am nächsten kommt).

Async re-entrancy, and the patterns to deal with it