Forum Cross Platform with Xamarin

Can "await" create a new thread?

DEDDED ESMember
edited September 2013 in Cross Platform with Xamarin

Until now, I thought that "await" by itself did not create a new thread to run the awaited task. But taking a quick look at this I'm a bit confused, since the way some parts are worded makes me think that a new thread is actually created.

I know that the awaited task can create a new thread, and most library methods probably do, but let's say I have something like this:

async Task<int> GiveMeANumber()
{
    int a = 1;
    return a;
}

async Task DoSomething()
{
    int k = await GiveMeANumber();
}

Is there any possibility that the "await" in "DoSomething()" could create a new thread, even when "GiveMeANumber()" does not?

Posts

  • CheesebaronCheesebaron DKInsider, University mod

    Maybe this can help you understand async/await: http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx?Redirected=true

    But basically the Task's you create get put into the ThreadPool so that they can run asynchronously and not blocking the caller thread. This is done in some clever way such that threads get reused in order not to spawn too many of them.

  • DEDDED ESMember

    Thanks for the link; I've read many articles about async/await, but that one has some bits that are clearer, and now I think I have most of the pieces of the puzzle in their place. However, I'm afraid the way I see the part about threads differs from your view. This is the way I understand this:

    1) A method marked with "async" does not start executing on a new thread, it uses the same thread as the caller, no matter if it's called with "await" or not.

    2) The code after an "await" may or may not execute on a different thread than the code before it:

    2a) If there is a "SynchronizationContext", and "ConfigureAwait(false)" is not used, the code after "await" will be executed on the same thread as the code before it (that is, will be scheduled to run in the future on the same thread).

    2b) If there isn't a "SynchronizationContext", or "ConfigureAwait(false)" is used, the code after "await" may use a new thread from the thread pool, though it depends on some factors.

    So what I assume is that, in the presence of a "SynchronizationContext", without using any "ConfigureAwait(false)" and without explicitly running any of my code in a new thread (e.g., using "Task.Run()"), all my code will always run on the same thread (the UI thread).

    In the blog post linked, Stephen says:

    When you invoke a method marked as “async”, it begins running synchronously on the curren thread.

    if you use “await task;” on the UI thread of your application, OnCompleted when invoked will see a non-null current SynchronizationContext, and when the task completes, it’ll use that UI’s SynchronizationContext to marshal the invocation of the continuation delegate back to the UI thread.

    If there isn’t a current SynchronizationContext when you await a Task, then the system will check to see if there’s a current TaskScheduler, and if there is, the continuation will be scheduled to that when the task completes.

    If there isn’t such a context or scheduler to force the continuation back to, or if you do “await task.ConfigureAwait(false)” instead of just “await task;”, then the continuation won’t be forced back to the original context and will be allowed to run wherever the system deems appropriate. This typically means either running the continuation synchronously wherever the awaited task completes or running the continuation on the ThreadPool.

    I've done a pair of quick tests with Visual Studio, a console application (which doesn't have a "SynchronizationContext") and a Windows Forms one (which does have a "SynchronizationContext"), and the behaviour is the same as I expected.

    This is the console application:

    static void Main(string[] args)
    {
        Console.WriteLine("Main thread before: " + Thread.CurrentThread.ManagedThreadId);
        MethodOne();
        Console.WriteLine("Main thread after: " + Thread.CurrentThread.ManagedThreadId);
    
        Console.ReadKey();
    }
    
    
    static async Task MethodOne()
    {
        Console.WriteLine("MethodOne thread before: " + Thread.CurrentThread.ManagedThreadId);
        int a = await MethodTwo();
        Console.WriteLine("MethodOne thread after: " + Thread.CurrentThread.ManagedThreadId);
    }
    
    
    static async Task<int> MethodTwo()
    {
        Console.WriteLine("MethodTwo thread before: " + Thread.CurrentThread.ManagedThreadId);
        await Task.Run(() => {});   // This creates a new thread.
        Console.WriteLine("MethodTwo thread after: " + Thread.CurrentThread.ManagedThreadId);
        return 2;
    }
    

    And this is the output:

    Main thread before: 8

    MethodOne thread before: 8

    MethodTwo thread before: 8

    MethodTwo thread after: 9

    Main thread after: 8

    MethodOne thread after: 10

    If I discard the "await Task.Run" line, which starts a new thread, I get this instead:

    Main thread before: 9

    MethodOne thread before: 9

    MethodTwo thread before: 9

    MethodTwo thread after: 9

    MethodOne thread after: 9

    Main thread after: 9

    Looks like when you "await" a task that creates a new thread, the continuation runs in a different thread, which doesn't happen if there are no new threads explicitly created, though I can't be sure if that will always happen. The thing is that here, without a "SynchronizationContext", you could have the code after the "await" executed on a different thread.

    Now, this is a snippet of the Forms application. There's a button to start the test, and a text box to get the results:

    private void button1_Click(object sender, EventArgs e)
    {
        textBox1.AppendText("button1_Click thread before: " + Thread.CurrentThread.ManagedThreadId + "\n");
        MethodOne();
        textBox1.AppendText("button1_Click thread after: " + Thread.CurrentThread.ManagedThreadId + "\n");
    }
    
    
    async Task MethodOne()
    {
        textBox1.AppendText("MethodOne thread before: " + Thread.CurrentThread.ManagedThreadId + "\n");
        int a = await MethodTwo();
        textBox1.AppendText("MethodOne thread after: " + Thread.CurrentThread.ManagedThreadId + "\n");
    }
    
    
    async Task<int> MethodTwo()
    {
        textBox1.AppendText("MethodTwo thread before: " + Thread.CurrentThread.ManagedThreadId + "\n");
        await Task.Run(() => {});   // This creates a new thread.
        textBox1.AppendText("MethodTwo thread after: " + Thread.CurrentThread.ManagedThreadId + "\n");
        return 2;
    }
    

    And here the output:

    button1_Click thread before: 9

    MethodOne thread before: 9

    MethodTwo thread before: 9

    button1_Click thread after: 9

    MethodTwo thread after: 9

    MethodOne thread after: 9

    So all my code runs on the same thread (except for what I put on "Task.Run()") thanks to the "SynchronizationContext" that exists on Windows Forms apps.

    Now, if I changed MethodTwo() to use "ConfigureAwait(false)", thus saying that I don't need to continue executing the remainder of the method within the same context, like this:

    async Task<int> MethodTwo()
    {
        textBox1.AppendText("MethodTwo thread before: " + Thread.CurrentThread.ManagedThreadId + "\n");
        await Task.Run(() => {}).ConfigureAwait(false);
    
        // This part will not run on the same thread:
        try
        {
            textBox1.AppendText("MethodTwo thread after: " + Thread.CurrentThread.ManagedThreadId + "\n");
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debugger.Break();  // The program will stop here.
        }
        return 2;
    }
    

    The application will stop because an exception is thrown, since I'm trying to access the UI (write into the text box) from a different thread to the UI one.

    So far, everything runs as I expected, but I would like to know if this behaviour is just specific to MS's implementation or if that's the standard (I guess it's the latter, but I would like to clarify it), if Xamarin also would behave this way, and if it's got the "SynchronizationContext" so that I don't have to worry about executing UI code on a different thread.

  • DEDDED ESMember
    edited September 2013

    Just a clarification: apparently, a SynchronizationContext does not need to make the continuation code after the "await" execute on the same thread, but most implementations on UI frameworks, such as the ones Windows Forms or WPF use, do:

    blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx

    Does Xamarin behave that way?

Sign In or Register to comment.