Reputation: 4919
I want status updates from a long running method. Usually I'd use the dispatcher to post back to the UI thread, but I'm curious about using async await.
To keep it simple:
Create a window, add a button
<Button Name="ButtonWithCodeBehind" Height="25" Click="ButtonWithCodeBehindOnClick"/>
add some code behind onClick handler
private async void ButtonWithCodeBehindOnClick(object sender, RoutedEventArgs e)
{
await Task.Factory.StartNew(() =>
{
ButtonWithCodeBehind.Content = "First";
Thread.Sleep(1000);
ButtonWithCodeBehind.Content = "Second";
Thread.Sleep(1000);
ButtonWithCodeBehind.Content = "Third";
});
}
This will obviously break because ButtonWithCodeBehind.Content will be accessed on the wrong thread.
Is there a way to make this work without doing something like:
Deployment.Current.Dispatcher.BeginInvoke(()=>ButtonWithCodeBehind.Content = "Second");
The critical thing here is the long running task will be generating updates as it progresses, I can refactor the code to something like this:
private async void ButtonWithCodeBehindOnClick(object sender, RoutedEventArgs e)
{
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
await Task.Factory.StartNew(() =>
{
Task.Factory.StartNew(() => Thread.Sleep(1000))
.ContinueWith(t => ButtonWithCodeBehind.Content = "First", scheduler)
.ContinueWith(t => Thread.Sleep(1000))
.ContinueWith(t => ButtonWithCodeBehind.Content = "Second", scheduler)
.ContinueWith(t => Thread.Sleep(1000))
.ContinueWith(t => ButtonWithCodeBehind.Content = "Third", scheduler);
});
}
But this is fugly. Also, if you took out the async and await keywords and replaced them with Task.WaitAll, it would still execute as expected.
Note: If you're wondering why I'm using Thread.Sleep instead of Task.Delay, I'm actually testing this in Silverlight as well and the async await support doesn't include .Delay (or at least not where I expect it to be).
Upvotes: 0
Views: 764
Reputation: 994
The only part that needs to be awaited is the long-running part - the IO call or, in this instance, the CPU-bound sleep.
private async void ButtonWithCodeBehindOnClick(object sender, RoutedEventArgs e)
{
ButtonWithCodeBehind.Content = "First";
await Task.Factory.StartNew(() => Thread.Sleep());
ButtonWithCodeBehind.Content = "Second";
await Task.Factory.StartNew(() => Thread.Sleep());
ButtonWithCodeBehind.Content = "Third";
}
Await captures the synchronization context and ensures that the rest of the method is signed up to a continuation which is run on a thread with the same context. In WPF, the UI thread handles the code ButtonWithCodeBehindOnClick
and thus, by default, will be responsible for the rest of the method call after await
.
You can override this default behaviour by configuring the await on the task:
await Task.Factory.StartNew(() =>
Thread.Sleep()).ConfigureAwait(continueOnCapturedContext: false);
However, you absolutely don't want to do this in WPF as a threadpool thread will try to update your UI.
Upvotes: 1
Reputation: 2929
If you can split up your long running task into two distinct long running actions (like two Thread.Sleeps in your example above) you can await each long running task on it's own. So the UI updates will be performed on the UI thread.
private async void ButtonWithCodeBehindOnClick(object sender, RoutedEventArgs e)
{
ButtonWithCodeBehind.Content = "First";
await Task.Run(() => Thread.Sleep(1000));
ButtonWithCodeBehind.Content = "Second";
await Task.Run(() => Thread.Sleep(1000));
ButtonWithCodeBehind.Content = "Third";
}
Upvotes: 2