Problem
There’s a button in my C#/XAML metro app that starts a long-running operation. As a result, I’m using async/await to ensure that the UI thread isn’t 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 events in GetResults will necessitate extra user input before proceeding. Let’s pretend the user only needs to click a “proceed” button.
My issue is: how can I pause GetResults execution while waiting for an event, such as the click of another button?
Here’s an unattractive approach to get what I want: The continue” button’s event handler sets a flag…
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
GetResults polls it on a regular basis to see what’s new:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
I’m searching for something event-based because the polling is clearly bad (busy waiting / waste of cycles).
Any ideas?
In this simplistic example, splitting GetResults() into two parts and invoking the first part from the start button and the second part from the continue button would be one option. In fact, the events in GetResults are more complicated, and various sorts of user interaction may be requested at various points throughout the execution. As a result, dividing the logic into many methods would be difficult.
Asked by Max
Solution #1
As a signal, you can use an instance of the SemaphoreSlim Class:
private SemaphoreSlim signal = new SemaphoreSlim(0, 1);
// set signal in event
signal.Release();
// wait for signal somewhere else
await signal.WaitAsync();
Alternatively, you may create a TaskT> that represents the outcome of the button click by using an instance of the TaskCompletionSourceT> Class:
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
// complete task in event
tcs.SetResult(true);
// wait for task somewhere else
await tcs.Task;
Answered by dtb
Solution #2
When you have an uncommon thing to wait for, TaskCompletionSource is frequently the simplest solution (or some async-enabled primitive based on TaskCompletionSource).
Because your requirements are straightforward in this situation, you can just use 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);
}
TaskCompletionSource is logically similar to an async ManualResetEvent, with the exception that you can only “set” the event once and it can have a “result” (which we don’t use in this case).
Answered by Stephen Cleary
Solution #3
I use the following utility class:
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; }
}
Here’s how I put it to use:
var itChanged = new AsyncEventListener();
someObject.PropertyChanged += itChanged.Listen;
// ... make it change ...
await itChanged.Successfully;
someObject.PropertyChanged -= itChanged.Listen;
Answered by Anders Skovborg
Solution #4
In an ideal world, you wouldn’t. While you can surely block the async thread, this is inefficient and ineffective.
Consider the classic scenario in which the user takes a break while the button is waiting to be pressed.
If you’ve paused your asynchronous code while waiting for user input, you’re just wasting resources while that thread is idle.
That said, it’s preferable if you set the state you need to preserve in your asynchronous action to the point where the button is enabled and you’re “waiting” for a click. Your GetResults function will then come to a halt.
Then, when the button is pressed, you start another asynchronous task to carry on the work, based on the state you’ve saved.
Because the SynchronizationContext will be captured in the event handler that calls GetResults (due to the use of the await keyword and the fact that SynchronizationContext.Current should be non-null in a UI application), you can use async/await in the following way:
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)
...
}
ContinueToGetResults When your button is pressed, async is the technique that continues to get the results. Your event handler does nothing if your button isn’t pressed.
Answered by casperOne
Solution #5
Simple Helper Class:
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;
Answered by Felix Keil
Post is based on https://stackoverflow.com/questions/12858501/is-it-possible-to-await-an-event-instead-of-another-async-method