personnalisé - raise event c#




Est-il possible d'attendre un événement au lieu d'une autre méthode asynchrone? (5)

Idéalement, vous ne le faites pas . Alors que vous pouvez certainement bloquer le thread asynchrone, c'est un gaspillage de ressources, et pas idéal.

Considérons l'exemple canonique où l'utilisateur va déjeuner alors que le bouton attend d'être cliqué.

Si vous avez interrompu votre code asynchrone en attendant l'entrée de l'utilisateur, il gaspille des ressources alors que ce thread est en pause.

Cela dit, il vaut mieux que dans votre opération asynchrone, vous définissiez l'état que vous devez maintenir au point où le bouton est activé et vous «attendez» sur un clic. À ce stade, votre méthode GetResults s'arrête .

Ensuite, lorsque le bouton est cliqué, en fonction de l'état que vous avez stocké, vous démarrez une autre tâche asynchrone pour continuer le travail.

Car le SynchronizationContext sera capturé dans le gestionnaire d'événements qui appelle GetResults (le compilateur le fera à la suite de l'utilisation du mot-clé GetResults utilisé, et le fait que SynchronizationContext.Current doit être non nul, étant donné que vous êtes dans une application d'interface utilisateur ), vous pouvez utiliser async/await comme ceci:

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 les résultats dans l'événement où votre bouton est poussé. Si votre bouton n'est pas enfoncé, votre gestionnaire d'événements ne fait rien.

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 UI ne soit 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)
  ...
}

De temps en temps, les choses qui se passent dans GetResults nécessiteraient une entrée supplémentaire de l'utilisateur avant de pouvoir continuer. Pour simplifier, 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 sorte qu'il attend un événement tel que le clic d'un autre bouton?

Voici une façon laide de réaliser ce que je cherche: le gestionnaire d'évènement pour le bouton "continuer" met 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 (occupé en attente / perte de cycles) et je cherche quelque chose basé sur l'événement.

Des idées?

Btw dans cet exemple simplifié, une solution serait bien sûr de diviser GetResults () en deux parties, invoquer la première partie du bouton de démarrage et la deuxième partie du bouton continuer. En réalité, les choses qui se passent dans GetResults sont plus complexes et différents types d'entrées utilisateur peuvent être requis à différents points de l'exécution. Donc briser la logique en plusieurs méthodes serait non trivial.


Lorsque vous avez une chose inhabituelle que vous devez await , la réponse la plus simple est souvent TaskCompletionSource (ou une primitive activée par async basée sur TaskCompletionSource ).

Dans ce cas, votre besoin est assez simple, vous pouvez donc utiliser directement TaskCompletionSource :

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 est comme un ManualResetEvent async , 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 mettons juste le résultat à null ) .


Voici une classe d'utilité que j'utilise:

public class AsyncEventListener
{
    private readonly Func<bool> _predicate;

    public AsyncEventListener() : this(() => true)
    {

    }

    public AsyncEventListener(Func<bool> predicate)
    {
        _predicate = predicate;
        Successfully = new Task(() => { });
    }

    public void Listen(object sender, EventArgs eventArgs)
    {
        if (!Successfully.IsCompleted && _predicate.Invoke())
        {
            Successfully.RunSynchronously();
        }
    }

    public Task Successfully { get; }
}

Et voici comment je l'utilise:

var itChanged = new AsyncEventListener();
someObject.PropertyChanged += itChanged.Listen;

// ... make it change ...

await itChanged.Successfully;
someObject.PropertyChanged -= itChanged.Listen;

Vous pouvez utiliser une instance de la classe SemaphoreSlim en tant que signal:

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

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

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

Vous pouvez également utiliser une instance de la classe TaskCompletionSource <T> pour créer une Task<T> représentant 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;

Classe d'assistance simple:

public class EventAwaiter<TEventArgs>
{
    private readonly TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>();

    private readonly Action<EventHandler<TEventArgs>> _unsubscribe;

    public EventAwaiter(Action<EventHandler<TEventArgs>> subscribe, Action<EventHandler<TEventArgs>> unsubscribe)
    {
        subscribe(Subscription);
        _unsubscribe = unsubscribe;
    }

    public Task<TEventArgs> Task => _eventArrived.Task;

    private EventHandler<TEventArgs> Subscription => (s, e) =>
        {
            _eventArrived.TrySetResult(e);
            _unsubscribe(Subscription);
        };
}

Usage:

var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(
                            h => example.YourEvent += h,
                            h => example.YourEvent -= h);
await valueChangedEventAwaiter.Task;






windows-store-apps