Eduardo Wada
Eduardo Wada

Reputation: 2647

Is it safe to change ui properties after await in async methods?

I was messing up with c#'s await/async when I wrote the following program:

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        Test();

        while (true) { }
    }

    static async void Test()
    {
        var t = Task.Run(()=> Thread.Sleep(1000));

        await t;

        throw new Exception("This exception happens in a worker thread");
    }
}

When I run this program, I can clearly see in Visual Studio's thread window that the exception happens in a Worker Thread, rather than the Main Thread, this leads me to believe that when I await a task, my method can be finished by another thread.

However, when I open a new windows forms application and add the following event handler:

    async void button1_Click(object sender, EventArgs e)
    {
        var task = Task.Run(() => Thread.Sleep(1000));

        await task;

        button1.Text = "I'm in the main thread!"; //this works
    }

This works, so I notice that in a console application my Test function is resumed by a worker thread, while in a Windows Forms application, it's always resumed by the main thread.

Why is this? Is it reliable?

Upvotes: 1

Views: 948

Answers (3)

Stephen Cleary
Stephen Cleary

Reputation: 457127

As I explain in my async intro, by default await will capture a "context" and resume execution of its async method in that context.

Technically, this context is SynchronizationContext.Current unless it is null, in which case it is TaskScheduler.Current.

In everyday terms, this means that the context is a UI context if the method is running on a UI thread; it is an ASP.NET request context if the method is servicing an ASP.NET request; and it is most likely the thread pool context in all other situations.

Note that this is default behavior. You can specify that the method does not need to resume on its context by awaiting the result of ConfigureAwait(continueOnCapturedContext: false). In this case, it is likely that the rest of the method will be executed on a thread pool thread; but technically this just means that the method will be executed "somewhere" and you don't care where.

Upvotes: 2

Bradley Uffner
Bradley Uffner

Reputation: 17001

Yes, this is correct and one of the intended uses of async / await. The Task that you are awaiting is what happens asynchronously. When it resumes, it resumes on the calling thread, which will be the UI thread in the case of a WinForms event handler.

Note that it is possible for you to change this behavior using Task.ConfigureAwait(False). When the task is configured this way, it will not pass control back to the original thread and will instead resume on the task's thread.

All of this behavior is dependant on the application's current SynchonizationContext though. Some application types will have different contexts which can subtly change the behaviors. Console Applications are a good example of this, as they do not set a synchronization context by default, and will resume on the Task's thread. You can change this if you want by creating a context for the console app, as shown here.

Upvotes: 1

Yacoub Massad
Yacoub Massad

Reputation: 27871

Yes. It is reliable.

When you do await, the current SynchronizationContext is captured so that when it continues after the await, this context will be used to execute the code.

In GUI applications, the current SynchronizationContext is a context that will execute code on the UI thread.

In Console Applications, the current SynchronizationContext is null, therefore the Task continues on a thread-pool thread.

Take a look at this blog post.

Upvotes: 3

Related Questions