In meiner C # / XAML-U-Bahn-App gibt es eine Schaltfläche, die einen lang laufenden Prozess startet. Wie empfohlen verwende ich async / await, um sicherzustellen, dass der UI-Thread nicht blockiert wird:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
await GetResults();
}
private async Task GetResults()
{
// Do lot of complex stuff that takes a long time
// (e.g. contact some web services)
...
}
Gelegentlich erfordern die Vorgänge in GetResults zusätzliche Benutzereingaben, bevor sie fortgesetzt werden können. Nehmen wir zur Vereinfachung an, der Benutzer muss nur auf die Schaltfläche "Weiter" klicken.
Meine Frage ist: Wie kann ich die Ausführung von GetResults so aussetzen, dass sie auf ein Ereignis wie das Klicken einer anderen Schaltfläche wartet ?
Hier ist ein hässlicher Weg, um das zu erreichen, wonach ich suche: Der Event-Handler für die Schaltfläche "Weiter" setzt ein Flag ...
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
... und GetResults fragt es regelmäßig ab:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
Die Umfrage ist eindeutig schrecklich (beschäftigtes Warten / Verschwendung von Zyklen) und ich suche etwas Ereignisbasiertes.
Irgendwelche Ideen?
Übrigens wäre in diesem vereinfachten Beispiel eine Lösung natürlich, GetResults () in zwei Teile aufzuteilen, den ersten Teil über die Schaltfläche Start und den zweiten Teil über die Schaltfläche Weiter aufzurufen. In der Realität ist das Geschehen in GetResults komplexer und an verschiedenen Punkten der Ausführung können unterschiedliche Arten von Benutzereingaben erforderlich sein. Eine Aufteilung der Logik in mehrere Methoden wäre also nicht trivial.
ManualResetEvent(Slim)
scheint nicht zu unterstützenWaitAsync()
.async
bedeutet nicht "läuft auf einem anderen Thread" oder so ähnlich. Es bedeutet nur "Sie könnenawait
in dieser Methode verwenden". In diesem FallGetResults()
würde das Blockieren im Inneren den UI-Thread tatsächlich blockieren.await
an sich garantiert nicht, dass ein anderer Thread erstellt wird, aber alles andere nach der Anweisung wird als Fortsetzung des vonTask
Ihnen aufgerufenen oder erwarteten Ereignisses ausgeführtawait
. Meistens handelt es sich um eine Art asynchronen Vorgang, bei dem es sich um eine E / A-Vervollständigung handeln kann oder um etwas, das sich in einem anderen Thread befindet.SemaphoreSlim.WaitAsync
Schiebt das nicht einfachWait
auf einen Thread-Pool-Thread.SemaphoreSlim
hat eine richtige Warteschlange vonTask
s, die zur Implementierung verwendet werdenWaitAsync
.Wenn Sie eine ungewöhnliche Sache haben, die Sie tun müssen,
await
ist die einfachste Antwort oftTaskCompletionSource
(oder einasync
aktiviertes Grundelement basierend aufTaskCompletionSource
).In diesem Fall ist Ihr Bedarf recht einfach, sodass Sie ihn direkt verwenden
TaskCompletionSource
können:Logischerweise
TaskCompletionSource
ist es wie einasync
ManualResetEvent
, außer dass Sie das Ereignis nur einmal "setzen" können und das Ereignis ein "Ergebnis" haben kann (in diesem Fall verwenden wir es nicht, also setzen wir das Ergebnis einfach aufnull
).quelle
Hier ist eine Utility-Klasse, die ich verwende:
Und so benutze ich es:
quelle
new Task(() => { });
sofort fertig?Einfache Hilfsklasse:
Verwendung:
quelle
example.YourEvent
?Im Idealfall nicht . Sie können den asynchronen Thread zwar blockieren, dies ist jedoch eine Verschwendung von Ressourcen und nicht ideal.
Betrachten Sie das kanonische Beispiel, in dem der Benutzer zum Mittagessen geht, während die Schaltfläche darauf wartet, angeklickt zu werden.
Wenn Sie Ihren asynchronen Code angehalten haben, während Sie auf die Eingabe des Benutzers gewartet haben, werden nur Ressourcen verschwendet, während dieser Thread angehalten wird.
Das heißt, es ist besser, wenn Sie in Ihrem asynchronen Vorgang den Status, den Sie beibehalten müssen, so einstellen, dass die Schaltfläche aktiviert ist und Sie auf einen Klick "warten". An diesem Punkt stoppt Ihre
GetResults
Methode .Dann, wenn die Schaltfläche wird geklickt, basierend auf dem Zustand , dass Sie gespeichert haben, starten Sie eine weitere asynchrone Aufgabe , die Arbeit fortzusetzen.
Da das
SynchronizationContext
im Ereignishandler erfasst wird, der aufruftGetResults
(der Compiler tut dies aufgrund der Verwendung des verwendetenawait
Schlüsselworts und der Tatsache, dass SynchronizationContext.Current ungleich Null sein sollte, vorausgesetzt, Sie befinden sich in einer UI-Anwendung) kannasync
/await
mag so:ContinueToGetResultsAsync
ist die Methode, die weiterhin die Ergebnisse erhält, falls Ihre Taste gedrückt wird. Wenn Ihre Taste nicht gedrückt wird, unternimmt Ihr Event-Handler nichts.quelle
GetResults
gibt a zurückTask
.await
sagt einfach "Führen Sie die Aufgabe aus, und wenn die Aufgabe erledigt ist, setzen Sie den Code danach fort". Da es einen Synchronisationskontext gibt, wird der Aufruf zurück zum UI-Thread gemarshallt, da er auf dem Thread erfasst wirdawait
.await
ist nicht dasselbe wieTask.Wait()
, nicht im geringsten.Wait()
. Aber der Code inGetResults()
wird hier auf dem UI-Thread ausgeführt, es gibt keinen anderen Thread. Mit anderen Worten, ja,await
führt die Aufgabe im Grunde aus, wie Sie sagen, aber hier wird diese Aufgabe auch auf dem UI-Thread ausgeführt.await
und dem Code danachawait
, es gibt keine Blockierung. Der Rest des Codes wird in einer Fortsetzung zurückgeführt und über das Programm geplantSynchronizationContext
.Stephen Toub hat diese
AsyncManualResetEvent
Klasse in seinem Blog veröffentlicht .quelle
Mit reaktiven Erweiterungen (Rx.Net)
Sie können Rx mit Nuget Package System.Reactive hinzufügen
Getestete Probe:
quelle
Ich verwende meine eigene AsyncEvent-Klasse für erwartete Ereignisse.
So deklarieren Sie ein Ereignis in der Klasse, das Ereignisse auslöst:
Um die Ereignisse zu erhöhen:
So abonnieren Sie die Veranstaltungen:
quelle