Is it possible to await an event instead of another async method?

Max picture Max · Oct 12, 2012 · Viewed 73.6k times · Source

In my C#/XAML metro app, there's a button which kicks off a long-running process. So, as recommended, I'm using async/await to make sure the UI thread doesn't get blocked:

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)
  ...
}

Occasionally, the stuff happening within GetResults would require additional user input before it can continue. For simplicity, let's say the user just has to click a "continue" button.

My question is: how can I suspend the execution of GetResults in such a way that it awaits an event such as the click of another button?

Here's an ugly way to achieve what I'm looking for: the event handler for the continue" button sets a flag...

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

... and GetResults periodically polls it:

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

The polling is clearly terrible (busy waiting / waste of cycles) and I'm looking for something event-based.

Any ideas?

Btw in this simplified example, one solution would be of course to split up GetResults() into two parts, invoke the first part from the start button and the second part from the continue button. In reality, the stuff happening in GetResults is more complex and different types of user input can be required at different points within the execution. So breaking up the logic into multiple methods would be non-trivial.

Answer

dtb picture dtb · Oct 12, 2012

You can use an instance of the SemaphoreSlim Class as a signal:

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

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

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

Alternatively, you can use an instance of the TaskCompletionSource<T> Class to create a Task<T> that represents the result of the button click:

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

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

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