1

Await keyword is used with awaitable types (.NET comes with existing two such types, Task and Task<T>). However, it's possible to write your own awaitable type.

The msdn blog post states that:

you can think of the following code:

await FooAsync();
RestOfMethod();

as being similar in nature to this:

var t = FooAsync();
var currentContext = SynchronizationContext.Current;
t.ContinueWith(delegate
{
    if (currentContext == null)
        RestOfMethod();
    else
        currentContext.Post(delegate { RestOfMethod(); }, null);
}, TaskScheduler.Current);

Does the (pseudo)code above follow from the implementation of awaitable type (such as Task), or maybe is it just the way the compiler deals with any awaitable type that is on the right of the await keyword?

In the comments section below, there's a post explaining the difference between TaskScheduler and SynchronizationContext.

The primary difference is that SynchronizationContext is a general mechanism for working with delegates, whereas TaskScheduler is specific to and catered to Tasks (you can get a TaskScheduler that wraps a SynchronizationContext using TaskScheduler.FromCurrentSynchronizationContext). This is why awaiting Tasks takes both into account, first checking a SynchronizationContext (as the more general mechanism that most UI frameworks support), and then falling back to a TaskScheduler. Awaiting a different kind of object might choose to first use a SynchronizationContext, and then fall back to some other mechanism specific to that particular type.

If I understand the last sentence correctly, it means I can put any delegate I want in the continueWith method (I mean the t.ContinueWith call in code sample above), i.e. modify how await works when used with my custom awaitable object.

Just in case you want to know more: http://blogs.msdn.com/b/pfxteam/archive/2009/09/22/9898090.aspx

user4205580
  • 564
  • 5
  • 25

1 Answers1

4

Does the (pseudo)code above follow from the implementation of awaitable type (such as Task), or maybe is it just the way the compiler deals with any awaitable type that is on the right of the await keyword?

The pseudo code does follow from the implementation of tasks specifically. But it exists only to help you understand how it works because it's similar to continuations.

The actual code the compiler generates is widely different and it doesn't take the actual awaitable type into consideration. All it does is look for a GetAwaiter method that returns an awaiter that has IsCompleted, GetResult, OnCompleted. The awaitable implementation is the one that either captures SynchronizationContext.Current or not (or does something else entirely).

You can see the actual code here.

i.e. completely modify how await works when used with my custom awaitable object.

You can do whatever you like as long as it makes sense to you. You can even make built-in types awaitable with an extension method. For example this code:

public static Awaiter GetAwaiter(this string s)
{
    throw new NotImplementedException();
}
public abstract class Awaiter : INotifyCompletion
{
    public abstract bool IsCompleted { get; }
    public abstract void GetResult();
    public abstract void OnCompleted(Action continuation);
}

Will enable you to compile await "bar";. It will fail on runtime of course, but the compiler doesn't know that.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • Then I don't undestand what the last sentence I've quoted means: " Awaiting a different kind of object [different than Task, right?] might choose to first use a SynchronizationContext, and then fall back to some other mechanism specific to that particular type." – user4205580 Nov 17 '15 at 19:18
  • @user4205580 It means the awaitable type chooses what to do. The compiler just calls it. For example when you call `task.ConfigureAwait(false)` you get back a different awaitable called `ConfiguredTaskAwaitable` which depending on the boolean passed to the method chooses whether to use the `SynchronizationContext` or not. – i3arnon Nov 17 '15 at 19:21
  • @user4205580 So `await task` will post to the `SynchronizationContext` if there is one but `await task.ConfigureAwait(false)` will not. – i3arnon Nov 17 '15 at 19:22
  • @user4205580 It means that the fact that awaiting a `Task` captures the value of `SynchronizationContext.Current` is the behavior of the implementation of the awaiter for `Task`, and that a different awaiter can have different behavior. Capturing the current synchronization context isn't a language feature of `await`. – Servy Nov 17 '15 at 19:25
  • In that case, the `t.ContinueWith(delegate { if (currentContext == null) RestOfMethod(); else currentContext.Post(delegate { RestOfMethod(); }, null);` won't apply, because it doesn't try to run the continuation on the current context (I'm talking about your case, where `t` is a `ConfiguredTaskAwaitable`). So the quoted code _does_ change with implementation of awaitable. – user4205580 Nov 17 '15 at 19:25
  • @user4205580 1) It's saying that it's similar to, not exactly the same as, that code 2) It's describing what happens when you await a `Task`, it then explicitly says that if you await something *other* than a `Task`, it can do something different. 3) In that code snippet it *is* posting the continuation to the current sync context (if there is one). That's what the `currentContext.Post` call is there for. – Servy Nov 17 '15 at 19:26
  • @user4205580 this tries to explain how async-await "usually" works in .net. But that depends on the specific implementation of the awaitable and awaiter. The common case with async-await regards tasks but there are other awaitables that act differently (and you can create your own). The compiler is agnostic to that implementation. – i3arnon Nov 17 '15 at 19:29
  • @Servy, I know that the code snippet attemps to run the continuation on the current context, I just meant that this code might look different if the awaitable object is not a `Task`, and thus it doesn't have to run it on current context. – user4205580 Nov 17 '15 at 19:31
  • @user4205580 But the awaitable object *is* a task, and so it *does* run it on the current context, and it *does* call out the fact that if you `await` something that's not a `Task`, then this doesn't hold. I'm not seeing any problems with the MSDN page, nor what you're confused about. – Servy Nov 17 '15 at 19:33
  • @Servy if the awaitable object is a `Task`, then a very similar code will be executed (yes, it will run the cont. on current context). However, if it's not a `Task` (it can be a `ConfiguredTaskAwaitable`), then it doesn't have to attempt to run the continuation on current context. In short: this piece of code is true for `Task`s, it's not true for **any** awaitable object. I know that. Or maybe you mean something else? – user4205580 Nov 17 '15 at 19:37
  • @user4205580 And the page says as much in the comments below, so what's the problem? – Servy Nov 17 '15 at 20:07
  • There's no problem, I just didn't know if I understood it correctly. – user4205580 Nov 17 '15 at 20:10
  • @i3arnon to be precise, I think you meant 'The **awaiter** implementation is the one that either captures SynchronizationContext.Current or not (or does something else entirely).' - based on the answer to [this](http://stackoverflow.com/questions/12661348/custom-awaitables-for-dummies) – user4205580 Nov 18 '15 at 08:24
  • @user4205580 the awaitable controls which awaiter is returned. You could capture the SC in the awaitable if you wanted to... It doesn't really matter. – i3arnon Nov 18 '15 at 08:31
  • So capturing the SC by Task most likely happens in its `getAwaiter` method, right? Because this is the last called method of awaitable object before it returns to its caller. [source](http://stackoverflow.com/questions/12661348/custom-awaitables-for-dummies) – user4205580 Nov 18 '15 at 21:20
  • @user4205580 actually no. It happens in `SetContinuationForAwait` which is called from `OnCompleted` on the awaiter. [There's no need to guess](http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,2982). – i3arnon Nov 18 '15 at 23:04