16

I heard somewhere that C# 5 async-await will be so awesome that you will not have to worry about doing this:

if (InvokeRequired)
{
    BeginInvoke(...);
    return;
}
// do your stuff here

It looks like the callback of a await operation will happen in the original thread of the caller. It has been stated several times by Eric Lippert and Anders Hejlsberg that this feature originated from the need to make UIs (particularly touch device UIs) more responsive.

I think a common usage of such feature would be something like this:

public class Form1 : Form
{
    // ...
    async void GetFurtherInfo()
    {
        var temperature = await GetCurrentTemperatureAsync();
        label1.Text = temperature;
    }
}

If only a callback is used then setting the label text will raise an exception because it's not being executed in the UI's thread.

So far I couldn't find any resource confirming this is the case. Does anyone know about this? Are there any documents explaining technically how this will work?

Please provide a link from a reliable source, don't just answer "yes".

Alex
  • 3,228
  • 1
  • 22
  • 25
  • This seems highly unlikely, at least insofar as the `await` functionality is concerned. It's just a lot of syntactic sugar for [continuation passing](http://en.wikipedia.org/wiki/Continuation-passing_style). Possibly there are some other unrelated improvements to WinForms that are supposed to help? That would fall under the .NET framework itself, though, and not C# specifically. – Aaronaught Oct 16 '11 at 16:18
  • @Aaronaught I agree, this is why I'm asking the question precisely. I edited the question to make clear where I'm coming from. Sounds weird that they would create this feature and still require that we use the infamous InvokeRequired style of code. – Alex Oct 16 '11 at 16:48

3 Answers3

18

I think you're getting a few things confused, here. What you're asking for is already possible using System.Threading.Tasks, the async and await in C# 5 are just going to provide a little nicer syntactic sugar for the same feature.

Let's use a Winforms example - drop a button and a textbox on the form and use this code:

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => DelayedAdd(5, 10))
        .ContinueWith(t => DelayedAdd(t.Result, 20))
        .ContinueWith(t => DelayedAdd(t.Result, 30))
        .ContinueWith(t => DelayedAdd(t.Result, 50))
        .ContinueWith(t => textBox1.Text = t.Result.ToString(),
            TaskScheduler.FromCurrentSynchronizationContext());
}

private int DelayedAdd(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

Run it and you'll see that (a) it doesn't block the UI thread and (b) you don't get the usual "cross-thread operation not valid" error - unless you remove the TaskScheduler argument from the last ContinueWith, in which case you will.

This is bog-standard continuation passing style. The magic happens in the TaskScheduler class and specifically the instance retrieved by FromCurrentSynchronizationContext. Pass this into any continuation and you tell it that the continuation must run on whichever thread called the FromCurrentSynchronizationContext method - in this case, the UI thread.

Awaiters are slightly more sophisticated in the sense that they're aware of which thread they started on and which thread the continuation needs to happen on. So the above code can be written a little more naturally:

private async void button1_Click(object sender, EventArgs e)
{
    int a = await DelayedAddAsync(5, 10);
    int b = await DelayedAddAsync(a, 20);
    int c = await DelayedAddAsync(b, 30);
    int d = await DelayedAddAsync(c, 50);
    textBox1.Text = d.ToString();
}

private async Task<int> DelayedAddAsync(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

These two should look very similar, and in fact they are very similar. The DelayedAddAsync method now returns a Task<int> instead of an int, and so the await is just slapping continuations onto each one of those. The main difference is that it's passing along the synchronization context on each line, so you don't have to do it explicitly like we did in the last example.

In theory the differences are a lot more significant. In the second example, every single line in the button1_Click method is actually executed in the UI thread, but the task itself (DelayedAddAsync) runs in the background. In the first example, everything runs in the background, except for the assignment to textBox1.Text which we've explicitly attached to the UI thread's synchronization context.

That's what's really interesting about await - the fact that an awaiter is able to jump in and out of the same method without any blocking calls. You call await, the current thread goes back to processing messages, and when it's done, the awaiter will pick up exactly where it left off, in the same thread it left off in. But in terms of your Invoke/BeginInvoke contrast in the question, I'm sorry to say that you should have stopped doing that a long time ago.

Aaronaught
  • 44,005
  • 10
  • 92
  • 126
  • That's very interesting @Aaronaught. I was aware of the continuation passing style but wasn't aware of this whole "synchronization context" thing. Is there any document linking this sync context to C# 5 async-await? I understand it's an existing feature but the fact they are using it by default sounds like a big deal, specially because it must have some major impacts in performance, right? Any further comments on that? Thanks for your answer by the way. – Alex Oct 16 '11 at 20:00
  • 1
    @Alex: For answers to all of those follow-up questions, I suggest you read [Async Performance: Understanding the Costs of Async and Await](http://msdn.microsoft.com/en-us/magazine/hh456402.aspx). The "Care About Context" section explains how it all relates to synchronization context. – Aaronaught Oct 16 '11 at 20:06
  • (By the way, synchronization contexts are not new; they've been in the framework since 2.0. The TPL just made them a lot easier to use.) – Aaronaught Oct 16 '11 at 20:08
  • 2
    I'm wondering why there is a lot of discussions on using InvokeRequired style still and most of the threads I've seen don't even mention sync contexts. It would have saved me the time to put this question up... – Alex Oct 16 '11 at 20:49
  • 2
    @Alex: I guess you weren't [looking in the right places](http://www.codeproject.com/KB/cpp/SyncContextTutorial.aspx). I don't know what to tell you; there are large parts of the .NET community which take a long time to catch up. Hell, I still see some coders using the `ArrayList` class in new code. I still barely have any experience with RX, myself. People learn what they need to know and share what they already know, even if what they already know is out of date. This answer might be out of date in a few years. – Aaronaught Oct 16 '11 at 21:37
  • 1
    Sync contexts are a bit harder to explain/grok, evidenced by a minor error in your answer: the default sync context of the thread pool executes callbacks on the thread pool in general, not necessarily "on whichever thread called the FromCurrentSynchronizationContext method". Potentially an important distinction if you're accessing thread local storage! – MaulingMonkey Dec 18 '12 at 22:29
  • @MaulingMonkey: Do you have a reference for that? I don't believe it to be correct; that is why several best-practice guides recommend using `ConfigureAwait(false)` at the end of `await` statements - to allow the continuation to run on any free thread in the thread pool. Or are you saying that this only applies to the UI threads in Winforms/WPF/etc., and not for example to an async MVC or Web API controller? – Aaronaught Dec 19 '12 at 02:18
  • While I don't yet grok ConfigureAwait's underpinnings, it's probably pointless if you're already on the thread pool, and would make sense only for UI threads. If you see this sample: https://gist.github.com/4334796 -- message boxes will spam after pressing the button once because the default sync context of the thread pool allows execution to continue on any thread of the pool. They'll disappear if you remove the wrapping ThreadPool.QueueUserWorkItem, because it then uses the default sync context of the UI thread, which limits execution to the UI thread. – MaulingMonkey Dec 19 '12 at 06:43
  • As for references, http://msdn.microsoft.com/en-us/magazine/gg598924.aspx notes that "Thus, UI applications usually have two synchronization contexts: the UI SynchronizationContext covering the UI thread, and the default SynchronizationContext covering the ThreadPool threads." -- updated example using FromCurrentSynchronizationContext explicitly here: https://gist.github.com/4334891 – MaulingMonkey Dec 19 '12 at 06:54
  • Further down if you Ctrl+F for "Summary of SynchronizationContext Implementations" you'll see a comparison table also showing "No" for "Specific Thread Used to Execute Delegates" x "Default". And of course, these are only the built in sync contexts, nevermind user defined ones. – MaulingMonkey Dec 19 '12 at 06:56
  • @MaulingMonkey: OK, I get what you're trying to explain, you're just referring to the fact that a synchronization context isn't necessarily a single thread, it's a way of sending messages to a different thread than the one you're on, and which thread actually responds is up to the synchronization context; it's analogous to the Windows message pump and even has a similar API. All .NET programmers should know that you can never assume a thread affinity when the thread pool is involved - not even when using `TaskContinuationOptions.ExecuteSynchronously`, which might not be honoured. – Aaronaught Dec 20 '12 at 04:31
  • @MaulingMonkey: But I think you're overlooking a vital point here, which is that there *is no synchronization context* when you're in the thread pool. That's arguably the more obvious reason why the continuation can run on any thread. Generally, a synchronization context *does* imply thread affinity. Try it yourself: Run `ThreadPool.QueueUserWorkItem(s => Console.WriteLine(SynchronizationContext.Current))` and you'll see `null` printed out. So it's not that the concept of a sync context is described incorrectly here (although it's a simplification), it's that it just doesn't always exist. – Aaronaught Dec 20 '12 at 04:35
  • Check again, the second gist above (this one: gist.github.com/4334891 ) explicitly sets a synchronization context above via SetSynchronizationContext on the thread pool thread in question to make the point more explicit. Your Console.WriteLine does NOT print null after such a call, yet it DOES still exhibit identical thread hopping behavior. – MaulingMonkey Dec 20 '12 at 07:58
  • @MaulingMonkey: You can't just create a `SynchronizationContext`. Well, you can, but it's pretty obvious what happens if you just read the documentation: *The SynchronizationContext class is a base class that provides a free-threaded context **with no synchronization.*** So, hardly surprising - there's not much difference between no synchronization context vs. a stub. *Practically* speaking, you're still running without a synchronization context. There's nothing special about the thread pool. If you used a proper SC, it would follow the behaviour defined by that SC. – Aaronaught Dec 21 '12 at 00:36
  • Even if we pretend that a SynchronizationContext is effectively not a synchronization context, the MSDN mag article points out AspNetSynchronizationContext as used by ASP.NET does exclusion of simultaneous execution (which you'd surely concede is a form of synchronization) without without being bound to a specific thread. Synchronization contexts are after all about synchronization, *not* about thread picking. The point stands. – MaulingMonkey Dec 21 '12 at 01:23
  • @MaulingMonkey: There's no pretending, it's right there in the documentation; that class a stub for when you need a `SynchronizationContext` but don't actually want any synchronization. Synchronization itself is about threads (and "thread picking"), so I have no idea what you're getting at. It's entirely up to the synchronization context implementation if it wants to invoke or continue on a UI thread, on a thread pool thread, or on any arbitrary thread based on whatever criteria it chooses. That's why the SC abstraction exists in the first place. It's effectively a message pump. – Aaronaught Dec 21 '12 at 21:15
  • I suppose it *is* technically incorrect to say that it runs on the calling thread; I'm pretty sure that what the dispatcher/forms context actually does is run it on the *UI* thread, period; the SC actually has no knowledge of the original thread, the `Post` and `Send` methods accept no such parameter, so maybe I will try to clarify in my answer. Practically speaking, though, events tend to get initiated from the UI thread which means they continuations happen on the same thread. – Aaronaught Dec 21 '12 at 21:21
4

Yes, for the UI thread, the callback of a await operation will happen on the original thread of the caller.

Eric Lippert wrote an 8-part series all about it a year ago: Fabulous Adventures In Coding

EDIT: and here's Anders' //build/ presentation: channel9

BTW, did you notice that if you turn "//build/" upside down, you get "/plinq//" ;-)

Glorfindel
  • 3,137
  • 6
  • 25
  • 33
Nick Butler
  • 303
  • 3
  • 8
3

Jon Skeet gave an excellent presentation covering Async methods in C#5 which you may find extremely useful:

http://skillsmatter.com/podcast/home/async-methods

Sean Holm
  • 264
  • 1
  • 3
  • [EduAsync](http://msmvps.com/blogs/jon_skeet/archive/tags/Eduasync/default.aspx) by the same author may also be a good source of information about Async. – Matthieu Dec 17 '11 at 01:45