async/await with ConfigureAwait's continueOnCapturedContext parameter and SynchronizationContext for asynchronous continuations
Asked Answered
I

1

11

I would like put the code first and then explain the situation and ask my question based on that:

public partial class MainWindow : Window {

    public MainWindow() {
        InitializeComponent();
    }

    private async void Button_Click_2(object sender, RoutedEventArgs e) {

        var result = await GetValuesAsync();
        Foo.Text += result;
    }

    public async Task<string> GetValuesAsync() {           

        using (var httpClient = new HttpClient()) {

            var response = await httpClient
                .GetAsync("http://www.google.com")
                .ConfigureAwait(continueOnCapturedContext: false);


            // This is the continuation for the httpClient.GetAsync method.
            // We shouldn't get back to sync context here
            // Cuz the continueOnCapturedContext is set to *false*
            // for the Task which is returned from httpClient.GetAsync method
            var html = await GetStringAsync();

            // This is the continuation for the GetStringAsync method.
            // Should I get back to sync context here?
            // Cuz the continueOnCapturedContext is set to *true*
            // for the Task which is returned from GetStringAsync 

            // However, GetStringAsync may be executed in another thread
            // which has no knowledge for the sync context 
            // because the continueOnCapturedContext is set to *false*
            // for the Task which is returned from httpClient.GetAsync method.

            // But, on the other hand, GetStringAsync method also has a 
            // chance to be executed in the UI thread but we shouldn't be
            // relying on that. 
            html += "Hey...";
            Foo.Text = html;

            return html;
        }
    }

    public async Task<string> GetStringAsync() {

        await Task.Delay(1000);
        return "Done...";
    }
}

This is a fairly simple WPF sample which runs on .NET 4.5 and probably doesn't make a lot of sense but this should help me explain my situation.

I have a button on the screen which has an asynchronous click event. When you look at the GetValuesAsync code, you will see the usage of await keyword twice. With the first usage, I set continueOnCapturedContext parameter of the Task.ConfigureAwait method to false. So, this indicates that I don't necessarily want my continuation to be executed inside the SynchronizationContext.Current. So far so good.

At the second await usage (with the GetStringAsync method), I didn't call the ConfigureAwait method. So, I basically indicated that I want to get back to current synchronization context for the continuation of the GetStringAsync method. So, as you can see, I try to set the TextBlock.Text (which belongs to UI thread) property inside the continuation.

When I run the application and click the button, I get an exception giving me the following message:

The calling thread cannot access this object because a different thread owns it.

At first, this made no sense to me and I thought that I discovered a bug but then, I realized that GetStringAsync may be executed in another thread (highly likely) which is different than the UI thread and has no knowledge for the sync context because the continueOnCapturedContext is set to false for the Task which is returned from httpClient.GetAsync method.

Is this the case here? Also, in this case, is there a chance for GetStringAsync method to be posted back to UI thread bacuse the httpClient.GetAsync method continuation may be executed inside the UI thread?

I have also a few comments inside the code. In view of my questions and the comments inside the code, am I missing anything here?

Intransigent answered 17/10, 2012 at 11:16 Comment(1)
C
11

When you call ConfigureAwait(false), the rest of the method will be executed on a thread pool thread unless the Task you're awaiting is already complete.

Since GetAsync will almost definitely run asynchronously, I would expect GetStringAsync to run on a thread pool thread.

public async Task<string> GetValuesAsync() {           

    using (var httpClient = new HttpClient()) {

        var response = await httpClient
            .GetAsync("http://www.google.com")
            .ConfigureAwait(continueOnCapturedContext: false);

        // And now we're on the thread pool thread.

        // This "await" will capture the current SynchronizationContext...
        var html = await GetStringAsync();
        // ... and resume it here.

        // But it's not the UI SynchronizationContext.
        // It's the ThreadPool SynchronizationContext.
        // So we're back on a thread pool thread here.

        // So this will raise an exception.
        html += "Hey...";
        Foo.Text = html;

        return html;
    }
}

Also, in this case, is there a chance for GetStringAsync method to be posted back to UI thread bacuse the httpClient.GetAsync method continuation may be executed inside the UI thread?

The only way GetStringAsync will run on the UI thread is if GetAsync completes before it's actually awaited. Highly unlikely.

For this reason, I prefer to use ConfigureAwait(false) for every await once the context is no longer needed.

Circumvallate answered 17/10, 2012 at 11:44 Comment(7)
Thanks! Your last sentence is the way I consider following (I have been but now I am going to insist more on that). this is not gonna make noticeable difference but using ConfigureAwait(false) will also save us from unnecessary SynchronizationContext checks if it is not needed.Intransigent
"Highly unlikely" is not enough, of course. Either it must be 100% or your code needs to tolerate both cases.Okay
I agree in general. That's why I prefer to use ConfigureAwait(false) for every await once the context is no longer needed.Circumvallate
if i have an async method that has if(await FileExistAsync(fileName)) should I use ConfigureAwait(false) in FileExistAsync?Striped
@LawrenceA.Contreras: You should decide for each method separately whether it should use ConfigureAwait(false). I always structure my code so that either a method always uses ConfigureAwait(false) or it never uses ConfigureAwait(false).Circumvallate
Is it safe to say that if the method will not need the UI context after await, then I shall use ConfigureAwait(false) to all await calls?Striped
@LawrenceA.Contreras: Yes.Circumvallate

© 2022 - 2024 — McMap. All rights reserved.