Skip to content

eyalcats/CrossThreadingTests

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Async/await in Desktop Applications

This repository contains a sample WinForms application to demonstrate async/await "challenges" in UI programming.

Demo App

The form contains three buttons which are intended to download the the content from http://microsoft.com asynchronously and show it in a TextBox.

Cross-Thread issue :(

private async void button1_Click(object sender, EventArgs e)
{
    // await a completed task => will continue synchronously
    await SomeFastAsyncOperation().ConfigureAwait(false);

    // await a slow task => will continue in another thread
    var t = await SomeSlowAsyncOperation().ConfigureAwait(false);

    // write text to text box
    textBox1.Text = t;
}

When clicking the first button, an InvalidOperationException will be thrown with the message

Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.

That's because in Win32 UIs you must access controls from the same thread that created them. Because the after awaiting SomeSlowAsyncOperation the method continues not in the main thread but a nackground thread, accessing textBox1 is forbidden.

When you debug button1_Click, pay attention to the Threads tool window.

Debugger

  1. Entering the method, you'll be on thread #1, the main thread.
  2. After calling SomeFastAsyncOperation, the code continues on thread #1. That's because the method returns an already completed task, so the code can continue synchronously.
  3. In contrast, SomeSlowAsyncOperation returns a not-completed task, therefore the succeeding code will continue not another one. (The UI thread will be released here and continue pumping the Win32 message queue)
  4. The property textBox1.Text will be set in said background thread and fail, because Win32 controls mist be accessed from the same thread that created them.

Blocking :(

private async void button2_Click(object sender, EventArgs e)
{
    // await a completed task => will continue synchronously
    await SomeFastAsyncOperation().ConfigureAwait(false);

    // await a slow task => will continue in another thread
    var t = SomeSlowAsyncOperation().ConfigureAwait(false).GetAwaiter().GetResult();

    // write text to text box
    textBox1.Text = t;
}

Clicking the second button will freeze the application. The GetAwaiter().GetResult() invocation will try to re-enter the the main thread, which is waiting for the task, so we'll run into a dead-lock.

Run smoothly :)

private async void button3_Click(object sender, EventArgs e)
{
    // force switch to threadpool thread
    await TaskScheduler.Default;

    // await a completed task => will continue synchronously
    await SomeFastAsyncOperation().ConfigureAwait(false);

    // await a slow task => will continue in another thread
    var t = await SomeSlowAsyncOperation().ConfigureAwait(false);

    // switch to main thread
    await _joinableTaskFactory.SwitchToMainThreadAsync();

    // write text to text box
    textBox1.Text = t;
}

Using the JoinableTaskFactory from Microsoft.VisualStudio.Threading (NuGet package here), we are able to "switch" back to the UI thread.

As the namespace implies, Microsoft.VisualStudio.Threading originates from the Visual Studio team. I stumbled over this library while reading the documentation for Visual Studio extensibility. Visual Studio is quite a complex application, and there are myriads of extensions available. To improve the start-up time, Microsoft strongly recommends to make use of asynchronous programming (see How to: Manage multiple threads in managed code and How to: Use AsyncPackage to load VSPackages in the background)

I won't go into details of how async/await works. Basically, the compiler generates a state machine, which Dixin explains pretty good in his blog serie Understanding C# async / await (1) Compilation (Part 2, Part 3).

In our case, two calls are interesting:

  1. await TaskScheduler.Default; will continue the succeeding code in a threadpool thread
    (actually, the library provides an extension method GetAwaiter(this TaskScheduler this). This works because the compiler uses a naming convention instead of requiring an interface implemenntation)
  2. await _joinableTaskFactory.SwitchToMainThreadAsync(); will continue the succeeding code in the main thread
    (actually, in the thread with instantiated _joinableTaskFactory).

As you could see, Microsoft.VisualStudio.Threading makes asynchronous programming in desktop applications, both WinForms and WPF, much simpler.

BTW, I've learned a lot reading the code of that library. It provides much more async helpers like AsyncEventHandlers.

Here are some more links if you want to learn more about async programming:

About

Async/await in Desktop Applications

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C# 100.0%