197 votes

Est-il possible d'attendre un événement à la place d'une autre méthode asynchrone?

Dans mon C#/XAML application de métro, il y a un bouton qui lance un long processus en cours d'exécution. Donc, comme l'a recommandé, je suis en utilisant async/await assurez-vous que le thread d'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 sont produites au sein de GetResults demanderait de l'entrée de l'utilisateur avant de pouvoir continuer. Pour simplifier, disons que l'utilisateur a juste à 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 fait de cliquer sur un autre bouton?

Voici une vilaine façon de réaliser ce que je suis à la recherche d': le gestionnaire d'événement pour la continuer" définit un indicateur...

private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
    _continue = true;
}

... et GetResults périodiquement des sondages:

 buttonContinue.Visibility = Visibility.Visible;
 while (!_continue) await Task.Delay(100);  // poll _continue every 100ms
 buttonContinue.Visibility = Visibility.Collapsed;

Le bureau de vote est clairement terrible (occupé attente / déchets de cycles) et je suis à la recherche de quelque chose basé sur des événements.

Des idées?

Btw dans cet exemple simplifié, une solution serait bien sûr de diviser GetResults() en deux parties, il faut appeler la première partie à partir du bouton démarrer et la deuxième partie à partir du bouton "continuer". En réalité, les choses qui se sont produites dans GetResults est plus complexe et les différents types de saisie de l'utilisateur peut être nécessaire à des moments différents de l'exécution. Afin de briser la logique dans plusieurs méthodes pourraient être non négligeable.

287voto

dtb Points 104373

Vous pouvez utiliser une instance de la SemaphoreSlim Classe comme un signal:

private SemaphoreSlim signal = new SemaphoreSlim(0, 1);

// set signal in event
signal.Release();

// wait for signal somewhere else
await signal.WaitAsync();

Alternativement, vous pouvez utiliser une instance de la TaskCompletionSource<T> la Classe pour créer une Task<T> qui représente le résultat du clic sur le bouton:

private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

// complete task in event
tcs.SetResult(true);

// wait for task somewhere else
await tcs.Task;

93voto

Stephen Cleary Points 91731

Quand vous avez une chose inhabituelle vous avez besoin d' await sur, la réponse la plus facile est souvent TaskCompletionSource (ou de certains d' async-permis primitif basé sur TaskCompletionSource).

Dans ce cas, votre besoin est assez simple, de sorte que vous pouvez simplement utiliser TaskCompletionSource directement:

private TaskCompletionSource<object> continueClicked;

private async void Button_Click_1(object sender, RoutedEventArgs e) 
{
  // Note: You probably want to disable this button while "in progress" so the
  //  user can't click it twice.
  await GetResults();
  // And re-enable the button here, possibly in a finally block.
}

private async Task GetResults()
{ 
  // Do lot of complex stuff that takes a long time
  // (e.g. contact some web services)

  // Wait for the user to click Continue.
  continueClicked = new TaskCompletionSource<object>();
  buttonContinue.Visibility = Visibility.Visible;
  await continueClicked.Task;
  buttonContinue.Visibility = Visibility.Collapsed;

  // More work...
}

private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
  if (continueClicked != null)
    continueClicked.TrySetResult(null);
}

Logiquement, TaskCompletionSource , c'est comme un async ManualResetEvent, sauf que l'on ne peut "régler" le cas une fois et que l'événement peut avoir un "résultat" (dans ce cas, nous ne l'utilisez pas, afin que nous venons de définir le résultat de la null).

5voto

casperOne Points 49736

Idéalement, vous n'avez pas. Alors vous avez certainement peut bloquer le thread asynchrone, c'est un gaspillage de ressources, et n'est pas idéal.

Considérons l'exemple canonique où l'utilisateur va pour le déjeuner, tandis que le bouton est en attente d'être cliqué.

Si vous avez interrompu votre code asynchrone lors de l'attente pour l'entrée de l'utilisateur, c'est tout simplement un gaspillage de ressources tandis que le thread est suspendu.

Cela dit, il est préférable si votre opération asynchrone, vous définissez l'état que vous avez besoin pour maintenir à l'endroit où le bouton est activé et que vous êtes "en attente" sur un clic. À ce stade, votre GetResults méthode s'arrête.

Puis, lorsque le bouton est cliqué, basé sur l'état que vous avez enregistré, vous commencer une autre tâche asynchrone pour continuer le travail.

Parce que l' SynchronizationContext sera capturé dans le gestionnaire d'événement qui appelle GetResults (le compilateur va faire cela comme un résultat de l'utilisation de l' await mot-clé utilisé, et le fait que SynchronizationContext.Actuel doit être non null, étant donné que vous êtes dans une application d'INTERFACE utilisateur), vous pouvez utiliser async/await comme:

private async void Button_Click_1(object sender, RoutedEventArgs e) 
{
     await GetResults();

     // Show dialog/UI element.  This code has been marshaled
     // back to the UI thread because the SynchronizationContext
     // was captured behind the scenes when
     // await was called on the previous line.
     ...

     // Check continue, if true, then continue with another async task.
     if (_continue) await ContinueToGetResultsAsync();
}

private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
    _continue = true;
}

private async Task GetResults()
{ 
     // Do lot of complex stuff that takes a long time
     // (e.g. contact some web services)
  ...
}

ContinueToGetResultsAsync est la méthode qui continue à obtenir des résultats dans le cas où le bouton est enfoncé. Si votre bouton n'est pas enfoncé, puis votre gestionnaire d'événement ne fait rien.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X