How to Run and Interact with an Async Task from a Wpf Gui

How to run and interact with an async Task from a WPF gui

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:

Basics of async and await

  1. You can only await in an async method.

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

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

The magic of async and await

  1. 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.

  2. The execution waits at await for the task to finish and returns back its results, without blocking the main thread.

  3. 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 awaiting the task you prevent the execution flow within that method from moving past the awaited 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();
}


Async method return types:

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

    • If you try writing await MethodAsync();, you will get a compile error saying:

    cannot await void

    • You can only fire and forget i.e. just call the method normally: MethodAsync(); and then go on with your life.
    • The 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.
    • This is how WPF handles your button click event handler, obviously because your event handler returns void.

The return type of an async method must be void, Task, Task<T>, a task-like type, IAsyncEnumerable<T>, or IAsyncEnumerator<T>



Wrapping and Unrwapping:

Wrapping:

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
}

Unwrapping:

To retrieve or unwrap the value which is wrapped inside a Task<>:

  • asynchronous option: await
  • synchronous option: 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>

Different ways to wrap and unwrap:

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".

Note:

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



WPF GUI:

This is where I explain how to run UI operations in another thread.

Blocking:

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
}

Thread Safety:

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

Patterns:

Fire and forget pattern:

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

Fire and observe:

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

Fire and wait synchronously while wasting thread resources:

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


Is that all to it?

No. There is a lot more to learn about async, its context and its continuation. This blogpost is especially recommended.

Task uses Thread? Are you sure?

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.

Read more

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

Async method blocks UI in WPF

You are incorrectly calling your async method from a button click event. It should be like this:

await Task.Run(()=> api.GetMyUserAsync());

Look at the answer here: How to run and interact with an async Task from a WPF gui

Using async and await that updates data in real time

Instead of using ps.Invoke() which is synchronous call and will wait for all results to return use ps.BeginInvoke() instead. Then subscribe to the DataAdded event of the output PSDataCollection and use the action to update your ui.

private async void run_click(object sender, RoutedEventArgs e)
{
Text1.Text = "";
await Task.Run(() => PS_Execution(Text1));
}

internal async Task PS_Execution(TextBox text)
{
using PowerShell ps = PowerShell.Create();
ps.AddScript(script.ToString());

PSDataCollection<string> input = null;
PSDataCollection<string> output = new();
IAsyncResult asyncResult = ps.BeginInvoke(input, output);

output.DataAdded += (sender, args) =>
{
var data = sender as PSDataCollection<string>;
text.Dispatcher.Invoke(() =>
{
text.Text += data[args.Index];
});

};
}

How to anychronously update a WPF control with results of long running calculation

The simplest way would be to have an asynchronous event handler for the control, perform the CPU-heavy work on another thread using something like Task.Run(), and update the label when done. Something like this would work:

private async void Control_Event(object sender, EventArgs e)
{
var result = await Task.Run(() => MyExpensiveCalculation());
label.Text = result;
}

private string MyExpensiveCalculation()
{
//Do your expensive calculation here and return the result.
}

Note that Task.Run() should be called from the UI thread. It should not be a part of the calculation implementation.

C#/WPF Using async and await but the UI thread is still blocked

The code you've shown won't block the UI thread.


In fact, as shown, it doesn't need async/await - so I'm assuming this is not the actual code.

You need to look at what ops.LongRunOp does with the progress function.

I suspect it marshals progress back to the UI thread - so it can access UI controls.


If it does this too often and too quickly, it will swamp the UI thread and make the app unresponsive.

What is recommended way to perform async tasks in WPF?

You can use several ways, for example:

  • Thread pool
  • Background Worker
  • Plain old threads

And since .NET 4, the preferred way is to use Tasks.



Related Topics



Leave a reply



Submit