Dans mon application de métro C # / XAML, il y a un bouton qui lance un processus de longue durée. Donc, comme recommandé, j'utilise async / await pour m'assurer que le thread de l'interface utilisateur n'est pas bloqué:
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)
...
}
Parfois, les choses qui se produisent dans GetResults nécessiteraient une entrée utilisateur supplémentaire avant de pouvoir continuer. Pour plus de simplicité, disons que l'utilisateur n'a qu'à cliquer sur un bouton "Continuer".
Ma question est: comment puis-je suspendre l'exécution de GetResults de telle manière qu'elle attend un événement tel que le clic d'un autre bouton?
Voici une manière moche de réaliser ce que je recherche: le gestionnaire d'événements pour le bouton "Continuer" définit un drapeau ...
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
... et GetResults l'interroge périodiquement:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
Le sondage est clairement terrible (attente chargée / perte de cycles) et je recherche quelque chose d'événementiel.
Des idées?
Btw dans cet exemple simplifié, une solution serait bien sûr de diviser GetResults () en deux parties, d'appeler la première partie à partir du bouton de démarrage et la deuxième partie à partir du bouton Continuer. En réalité, ce qui se passe dans GetResults est plus complexe et différents types d'entrée utilisateur peuvent être requis à différents moments de l'exécution. Donc, diviser la logique en plusieurs méthodes ne serait pas trivial.
ManualResetEvent(Slim)
ne semble pas soutenirWaitAsync()
.async
ne signifie pas «s'exécute sur un fil différent», ou quelque chose comme ça. Cela signifie simplement «vous pouvez utiliserawait
cette méthode». Et dans ce cas, le blocage à l'intérieur bloquerait enGetResults()
fait le thread de l'interface utilisateur.await
en lui-même ne garantit pas qu'un autre thread est créé, mais il fait que tout le reste après l'instruction s'exécute comme une continuation sur leTask
ou attendu que vous appelezawait
. Le plus souvent, il s'agit d' une sorte d'opération asynchrone, qui pourrait être l'achèvement d'E / S ou quelque chose qui se trouve sur un autre thread.SemaphoreSlim.WaitAsync
ne le pousse pas simplementWait
sur un thread de pool de threads.SemaphoreSlim
a une file d'attente appropriée deTask
s qui sont utilisées pour implémenterWaitAsync
.Lorsque vous avez une chose inhabituelle dont vous avez besoin
await
, la réponse la plus simple est souventTaskCompletionSource
(ou uneasync
primitive activée en fonction deTaskCompletionSource
).Dans ce cas, votre besoin est assez simple, vous pouvez donc simplement utiliser
TaskCompletionSource
directement:Logiquement,
TaskCompletionSource
c'est comme unasync
ManualResetEvent
, sauf que vous ne pouvez "définir" l'événement qu'une seule fois et que l'événement peut avoir un "résultat" (dans ce cas, nous ne l'utilisons pas, donc nous définissons simplement le résultat surnull
).la source
Voici une classe utilitaire que j'utilise:
Et voici comment je l'utilise:
la source
new Task(() => { });
terminé instantanément?Classe d'assistance simple:
Usage:
la source
example.YourEvent
?Idéalement, vous ne le faites pas . Bien que vous puissiez certainement bloquer le thread asynchrone, c'est un gaspillage de ressources et ce n'est pas idéal.
Prenons l'exemple canonique où l'utilisateur va déjeuner pendant que le bouton attend d'être cliqué.
Si vous avez arrêté votre code asynchrone en attendant l'entrée de l'utilisateur, cela ne fait que gaspiller des ressources pendant que ce thread est suspendu.
Cela dit, il est préférable que dans votre opération asynchrone, vous définissez l'état que vous devez maintenir au point où le bouton est activé et que vous «attendez» un clic. À ce stade, votre
GetResults
méthode s'arrête .Ensuite, lorsque le bouton est cliqué, basé sur l'état que vous avez enregistré, vous commencez une autre tâche asynchrone pour poursuivre les travaux.
Étant donné que le
SynchronizationContext
sera capturé dans le gestionnaire d'événements qui appelleGetResults
(le compilateur le fera à la suite de l'utilisation duawait
mot - clé utilisé et du fait que SynchronizationContext.Current doit être non nul, étant donné que vous êtes dans une application d'interface utilisateur), vous peut utiliserasync
/await
aimer ainsi:ContinueToGetResultsAsync
est la méthode qui continue d'obtenir les résultats dans le cas où votre bouton est enfoncé. Si votre bouton n'est pas enfoncé, votre gestionnaire d'événements ne fait rien.la source
GetResults
renvoie unTask
.await
dit simplement "exécutez la tâche, et lorsque la tâche est terminée, continuez le code après cela". Étant donné qu'il existe un contexte de synchronisation, l'appel est renvoyé au thread d'interface utilisateur, car il est capturé sur leawait
.await
n'est pas le même queTask.Wait()
, pas du tout.Wait()
. Mais le codeGetResults()
s'exécutera sur le thread d'interface utilisateur ici, il n'y a pas d'autre thread. En d'autres termes, oui,await
exécute essentiellement la tâche, comme vous le dites, mais ici, cette tâche s'exécute également sur le thread de l'interface utilisateur.await
, puis le code aprèsawait
, il n'y a pas de blocage. Le reste du code est réorganisé dans une continuation et planifié via leSynchronizationContext
.Stephen Toub a publié ce
AsyncManualResetEvent
cours sur son blog .la source
Avec les extensions réactives (Rx.Net)
Vous pouvez ajouter Rx avec Nuget Package System.
Échantillon testé:
la source
J'utilise ma propre classe AsyncEvent pour les événements attendus.
Pour déclarer un événement dans la classe qui déclenche des événements:
Pour élever les événements:
Pour vous abonner aux événements:
la source