Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 234
Max Avatar asked Oct 12 '12 11:10

Max


People also ask

Can you await a non async method?

You can not use the await keyword in a regular, non-async function. JavaScript engine will throw a syntax error if you try doing so.

What happens if we execute an asynchronous method but don't await it?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

Should you always await async methods?

If a method is declared async, make sure there is an await! If your code does not have an await in its body, the compiler will generate a warning but the state machine will be created nevertheless, adding unnecessary overhead for an operation that will actually never yield.

Can we use await without async in C#?

Every now and then you'll find yourself in a synchronous method (i.e. one that doesn't return a Task or Task<T> ) but you want to call an async method. However, without marking the method as async you can't use the await keyword.


2 Answers

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; 
like image 65
dtb Avatar answered Sep 25 '22 20:09

dtb


When you have an unusual thing you need to await on, the easiest answer is often TaskCompletionSource (or some async-enabled primitive based on TaskCompletionSource).

In this case, your need is quite simple, so you can just use TaskCompletionSource directly:

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

Logically, TaskCompletionSource is like an async ManualResetEvent, except that you can only "set" the event once and the event can have a "result" (in this case, we're not using it, so we just set the result to null).

like image 25
Stephen Cleary Avatar answered Sep 25 '22 20:09

Stephen Cleary