I have a WPF GUI, where I want to press a button to start a long task without freezing the window for the duration of the task. While the task is running I would like to get reports on progress, and I would like to incorporate another button that will stop the task at any time I choose.
I cannot figure the correct way to use async/await/task. I can't include everything I've tried, but this is what I have at the moment.
A WPF window class :
public partial class MainWindow : Window
{
readonly otherClass _burnBabyBurn = new OtherClass();
internal bool StopWorking = false;
//A button method to start the long running method
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
Task slowBurn = _burnBabyBurn.ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3);
await slowBurn;
}
//A button Method to interrupt and stop the long running method
private void StopButton_Click(object sender, RoutedEventArgs e)
{
StopWorking = true;
}
//A method to allow the worker method to call back and update the gui
internal void UpdateWindow(string message)
{
TextBox1.Text = message;
}
}
And a class for the worker method:
class OtherClass
{
internal Task ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
var tcs = new TaskCompletionSource<int>();
//Start doing work
gui.UpdateWindow("Work Started");
While(stillWorking)
{
//Mid procedure progress report
gui.UpdateWindow("Bath water n% thrown out");
if (gui.StopTraining) return tcs.Task;
}
//Exit message
gui.UpdateWindow("Done and Done");
return tcs.Task;
}
}
This runs, but the WPF function window is still blocked once the worker method starts.
I need to know how to arrange the async/await/task declarations to allow
A) the worker method to not block the gui window
B) let the worker method update the gui window
C) allow the gui window to stop interrupt and stop the worker method
Any help or pointers are much appreciated.
Async/Await on a Separate Thread If a predefined method returns a Task , you simply mark the calling method as async and put the await keyword in front of the method call. It's helpful to understand the control flow associated with the await keyword, but that's basically it.
An await expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method. The async and await keywords don't cause additional threads to be created.
The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete.
To start a task in C#, follow any of the below given ways. Use a delegate to start a task. Task t = new Task(delegate { PrintMessage(); }); t. Start();
Long story short:
private async void ButtonClickAsync(object sender, RoutedEventArgs e)
{
// modify UI object in UI thread
txt.Text = "started";
// run a method in another thread
await HeavyMethodAsync(txt);
// <<method execution is finished here>>
// modify UI object in UI thread
txt.Text = "done";
}
// This is a thread-safe method. You can run it in any thread
internal async Task HeavyMethodAsync(TextBox textBox)
{
while (stillWorking)
{
textBox.Dispatcher.Invoke(() =>
{
// UI operation goes inside of Invoke
textBox.Text += ".";
// Note that:
// Dispatcher.Invoke() blocks the UI thread anyway
// but without it you can't modify UI objects from another thread
});
// CPU-bound or I/O-bound operation goes outside of Invoke
// await won't block UI thread, unless it's run in a synchronous context
await Task.Delay(51);
}
}
Result:
started....................done
You need to know about (1) how to write async
code (2) how to run UI operations in another thread and (3) how to cancel a task.
I'm not getting into (3) cancellation mechanism in this post. Just know that you can create a CancellationTokenSource
, which gives you a CancellationToken
which you can pass into any method. You cancel the source, all tokens will know.
async
and await
:async
and await
You can only await
in an async
method.
You can only await
an awaitable object (i.e. Task
, ValueTask
, Task<T>
, IAsyncEnumerable<T>
, etc.) These objects wrap around the return type of an async
method and await
keyword unwraps them. (see Wrapping and Unwrapping section)
Asynchronous method names should always end with Async
to increase readability and to prevent mistakes.
// Synchronous method:
TResult MethodName(params) { }
// Asynchronous method:
async Task<TResult> MethodNameAsync(params) { }
async
and await
The async-await
syntactic feature, uses a state-machine to let the compiler give up and take back the control over the awaited Task
in an async
method.
The execution waits at await
for the task to finish and returns back its results, without blocking the main thread.
Task.Run
queues a Task
in the thread pool. (Unless the it's a pure operation.)
i.e. The async
method does not run in another thread. async
and await
by themselves don't have anything to do with thread creation.
So
When you run a Task
(e.g. Task.Run(action)
) you (re)use a thread for that action. And you can put that task in an async
method to control its flow. By putting async
in the method signature you tell the compiler to use state-machine to control the flow of that method (this does not mean threading at all). And by await
ing the task you prevent the execution flow within that method from moving past the await
ed statement without blocking UI thread. If you want to pass the flow onto the caller then the async
method itself can become a Task
so you'll be able to cascade the same pattern out into the caller and so forth:
async Task Caller() { await Method(); }
async Task Method() { await Inner(); }
async Task Inner() { await Task.Run(action); }
The event handler looks like the code below.
Two possible cases for presense of async in the signature of ExecuteLongProcedure
(case 1 and 2) and MyButton_ClickAsync
(case A and B) are explained:
private async void MyButton_ClickAsync(object sender, RoutedEventArgs e)
{
//queue a task to run on threadpool
// 1. if ExecuteLongProcedure is a normal method and returns void
Task task = Task.Run(()=>
ExecuteLongProcedure(this, intParam1, intParam2, intParam3)
);
// or
// 2. if ExecuteLongProcedure is an async method and returns Task
Task task = ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3);
// either way ExecuteLongProcedure is running asynchronously here
// the method will exit if you don't wait for the Task to finish
// A. wait without blocking the main thread
// -> requires MyButton_ClickAsync to be async
await task;
// or
// B. wait and block the thread (NOT RECOMMENDED AT ALL)
// -> does not require MyButton_ClickAsync to be async
task.Wait();
}
Suppose you have the following declaration:
private async ReturnType MethodAsync() { ... }
If ReturnType
is Task
then await MethodAsync();
returns void
If ReturnType
is Task<T>
then await MethodAsync();
returns a value of type T
This is called Unwrapping, see the next section (Wrapping and Unrwapping).
If ReturnType
is void
you can't await
it
await MethodAsync();
, you will get a compile error saying:cannot await void
MethodAsync();
and then go on with your life.MethodAsync
execution will be synchronous, however since it has async
it will allow you to take advantage of the magic, i.e. you can write await task
within the method to control the flow of execution.void
.The return type of an async method must be
void
,Task
,Task<T>
, a task-like type,IAsyncEnumerable<T>
, orIAsyncEnumerator<T>
async
methods wrap their return values in a Task
.
E.g., this method wraps a Task
around an int
and returns it:
// async Task<int>
private async Task<int> GetOneAsync()
{
int val = await CalculateStuffAsync();
return val;
// returns an integer
}
To retrieve or unwrap the value which is wrapped inside a Task<>
:
await
task.Result
or task.GetAwaiter().GetResult()
or task.WaitAndUnwrapException()
or read How to call asynchronous method from synchronous method in C#?
e.g. await
unwraps the int
out of the Task
:
Task<int> task = GetOneAsync();
int number = await task;
//int <- Task<int>
private Task<int> GetNumber()
{
Task<int> task;
task = Task.FromResult(1); // the correct way to wrap a quasi-atomic operation, the method GetNumber is not async
task = Task.Run(() => 1); // not the best way to wrap a number
return task;
}
private async Task<int> GetNumberAsync()
{
int number = await Task.Run(GetNumber); // unwrap int from Task<int>
// bad practices:
// int number = Task.Run(GetNumber).GetAwaiter().GetResult(); // sync over async
// int number = Task.Run(GetNumber).Result; // sync over async
// int number = Task.Run(GetNumber).Wait(); // sync over async
return number; // wrap int in Task<int>
}
Still confused? Read async return types on MSDN.
To unwrap a task result, Always try to use
await
instead of.Result
otherwise there will be no asynchronous benefit but only asynchronous disadvantages. The latter is called "sync over async".
await
is a asynchronous and is different from task.Wait()
which is synchronous. But they both do the same thing which is waiting for the task to finish.
await
is a asynchronous and is different from task.Result
which is synchronous. But they both do the same thing which is waiting for the task to finish and unwrapping and returning back the results.
To have a wrapped value, you can always use Task.FromResult(1)
instead of creating a new thread by using Task.Run(() => 1)
.
Task.Run
is newer (.NetFX4.5) and simpler version of Task.Factory.StartNew
This is where I explain how to run UI operations in another thread.
First thing you need to know about WPF async event handlers is that the Dispatcher
will provide a synchronization context. Explained here
CPU-bound or IO-bound operations such as Sleep
and task.Wait()
will block and consume the thread even if they are called in a method with async
keyword. but await Task.Delay()
tells the state-machine to stop the flow of execution on the thread so it does not consume it; meaning that the thread resources can be used elsewhere:
private async void Button_Click(object sender, RoutedEventArgs e)
{
Thread.Sleep(1000);//stops, blocks and consumes threadpool resources
await Task.Delay(1000);//stops without consuming threadpool resources
Task.Run(() => Thread.Sleep(1000));//does not stop but consumes threadpool resources
await Task.Run(() => Thread.Sleep(1000));//literally the WORST thing to do
}
If you have to access GUI asynchronously (inside ExecuteLongProcedure
method), invoke any operation which involves modification to any non-thread-safe object. For instance, any WPF GUI object must be invoked using a Dispatcher
object which is associated with the GUI thread:
void UpdateWindow(string text)
{
//safe call
Dispatcher.Invoke(() =>
{
txt.Text += text;
});
}
However, If a task is started as a result of a property changed callback from the ViewModel, there is no need to use Dispatcher.Invoke
because the callback is actually executed from the UI thread.
Accessing collections on non-UI Threads
WPF enables you to access and modify data collections on threads other than the one that created the collection. This enables you to use a background thread to receive data from an external source, such as a database, and display the data on the UI thread. By using another thread to modify the collection, your user interface remains responsive to user interaction.
Value changes fired by INotifyPropertyChanged are automatically marshalled back onto the dispatcher.
How to enable cross-thread access
Remember, async
method itself runs on the main thread. So this is valid:
private async void MyButton_ClickAsync(object sender, RoutedEventArgs e)
{
txt.Text = "starting"; // UI Thread
await Task.Run(()=> ExecuteLongProcedure1());
txt.Text = "waiting"; // UI Thread
await Task.Run(()=> ExecuteLongProcedure2());
txt.Text = "finished"; // UI Thread
}
Another way to invoke UI operations from UI thread is to use SynchronizationContext
as described here. SynchronizationContext
is a stronger abstraction than Dispatcher
and it's cross-platform.
var uiContext = SynchronizationContext.Current;
while (stillWorking)
{
uiContext.Post(o =>
{
textBox.Text += ".";
}, null);
await Task.Delay(51);
}
For obvious reasons this is how your WPF GUI event handlers such as Button_ClickAsync
are called.
void Do()
{
// CPU-Bound or IO-Bound operations
}
async void DoAsync() // returns void
{
await Task.Run(Do);
}
void FireAndForget() // not blocks, not waits
{
DoAsync();
}
Task-returning methods are better since unhandled exceptions trigger the TaskScheduler.UnobservedTaskException
.
void Do()
{
// CPU-Bound or IO-Bound operations
}
async Task DoAsync() // returns Task
{
await Task.Run(Do);
}
void FireAndWait() // not blocks, not waits
{
Task.Run(DoAsync);
}
This is known as Sync over async, it is a synchronous operation but it uses more than one thread which may cause starvation. This happens when you call Wait()
or try to read results directly from task.Result
before the task is finished.
(AVOID THIS PATTERN)
void Do()
{
// CPU-Bound or IO-Bound operations
}
async Task DoAsync() // returns Task
{
await Task.Run(Do);
}
void FireAndWait() // blocks, waits and uses 2 more threads. Yikes!
{
var task = Task.Run(DoAsync);
task.Wait();
}
No. There is a lot more to learn about async
, its context and its continuation. This blogpost is especially recommended.
Not necessarily. Read this answer to know more about the true face of async
.
Stephen Cleary has explained async-await
perfectly. He also explains in his other blog post when there is no thread involved.
ValueTask and Task
MSDN explains Task
MSDN explains async
how-to-call-asynchronous-method-from-synchronous-method
async await
- Behind the scenes
async await
- FAQ
Make sure you know the difference between Asynchronous, Parallel and Concurrent.
You may also read a simple asynchronous file writer to know where you should concurrent.
Investigate concurrent namespace
Ultimately, read this e-book: Patterns_of_Parallel_Programming_CSharp
Your use of TaskCompletionSource<T>
is incorrect. TaskCompletionSource<T>
is a way to create TAP-compatible wrappers for asynchronous operations. In your ExecuteLongProcedureAsync
method, the sample code is all CPU-bound (i.e., inherently synchronous, not asynchronous).
So, it's much more natural to write ExecuteLongProcedure
as a synchronous method. It's also a good idea to use standard types for standard behaviors, in particular using IProgress<T>
for progress updates and CancellationToken
for cancellation:
internal void ExecuteLongProcedure(int param1, int param2, int param3,
CancellationToken cancellationToken, IProgress<string> progress)
{
//Start doing work
if (progress != null)
progress.Report("Work Started");
while (true)
{
//Mid procedure progress report
if (progress != null)
progress.Report("Bath water n% thrown out");
cancellationToken.ThrowIfCancellationRequested();
}
//Exit message
if (progress != null)
progress.Report("Done and Done");
}
Now you have a more reusable type (no GUI dependencies) that uses the appropriate conventions. It can be used as such:
public partial class MainWindow : Window
{
readonly otherClass _burnBabyBurn = new OtherClass();
CancellationTokenSource _stopWorkingCts = new CancellationTokenSource();
//A button method to start the long running method
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
var progress = new Progress<string>(data => UpdateWindow(data));
try
{
await Task.Run(() => _burnBabyBurn.ExecuteLongProcedure(intParam1, intParam2, intParam3,
_stopWorkingCts.Token, progress));
}
catch (OperationCanceledException)
{
// TODO: update the GUI to indicate the method was canceled.
}
}
//A button Method to interrupt and stop the long running method
private void StopButton_Click(object sender, RoutedEventArgs e)
{
_stopWorkingCts.Cancel();
}
//A method to allow the worker method to call back and update the gui
void UpdateWindow(string message)
{
TextBox1.Text = message;
}
}
Here is an example using async/await
, IProgress<T>
and CancellationTokenSource
. These are the modern C# and .Net Framework language features that you should be using. The other solutions are making my eyes bleed a bit.
<Window x:Class="ProgressExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" SizeToContent="WidthAndHeight" Height="93.258" Width="316.945">
<StackPanel>
<Button x:Name="Button_Start" Click="Button_Click">Start</Button>
<ProgressBar x:Name="ProgressBar_Progress" Height="20" Maximum="100"/>
<Button x:Name="Button_Cancel" IsEnabled="False" Click="Button_Cancel_Click">Cancel</Button>
</StackPanel>
</Window>
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private CancellationTokenSource currentCancellationSource;
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
// Enable/disabled buttons so that only one counting task runs at a time.
this.Button_Start.IsEnabled = false;
this.Button_Cancel.IsEnabled = true;
try
{
// Set up the progress event handler - this instance automatically invokes to the UI for UI updates
// this.ProgressBar_Progress is the progress bar control
IProgress<int> progress = new Progress<int>(count => this.ProgressBar_Progress.Value = count);
currentCancellationSource = new CancellationTokenSource();
await CountToOneHundredAsync(progress, this.currentCancellationSource.Token);
// Operation was successful. Let the user know!
MessageBox.Show("Done counting!");
}
catch (OperationCanceledException)
{
// Operation was cancelled. Let the user know!
MessageBox.Show("Operation cancelled.");
}
finally
{
// Reset controls in a finally block so that they ALWAYS go
// back to the correct state once the counting ends,
// regardless of any exceptions
this.Button_Start.IsEnabled = true;
this.Button_Cancel.IsEnabled = false;
this.ProgressBar_Progress.Value = 0;
// Dispose of the cancellation source as it is no longer needed
this.currentCancellationSource.Dispose();
this.currentCancellationSource = null;
}
}
private async Task CountToOneHundredAsync(IProgress<int> progress, CancellationToken cancellationToken)
{
for (int i = 1; i <= 100; i++)
{
// This is where the 'work' is performed.
// Feel free to swap out Task.Delay for your own Task-returning code!
// You can even await many tasks here
// ConfigureAwait(false) tells the task that we dont need to come back to the UI after awaiting
// This is a good read on the subject - https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
// If cancelled, an exception will be thrown by the call the task.Delay
// and will bubble up to the calling method because we used await!
// Report progress with the current number
progress.Report(i);
}
}
private void Button_Cancel_Click(object sender, RoutedEventArgs e)
{
// Cancel the cancellation token
this.currentCancellationSource.Cancel();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With