4

I am quite confused with the subject. I am coming from assumption that task creation and its scheduling should be strictly separated which seams not to be the case in C#.

Consider the following simple code.

        static async Task simpleton()
        {
            print("simpleton");
        }

        static async Task cont()
        {
            print("cont");
        }

        static void Main(string[] args)
        {
            Task t1 = simpleton();

            Task t2 = t1.ContinueWith((a) => { cont(); });

            Thread.Sleep(1000);

            return;
        }

The output is

simpleton
cont

simpleton function runs and creates the task t1 (already completed) - that's ok. However the t2 seams to be (at least in code) only task creation - no scheduling was asked from the system, so why and who's schedules the continuation to run? Clearly the creation/running principle is broken here.

Similar situation is with await. Consider the pseudo code in this very well known article. According to pseudo code await is translated into the continuation task and returned to the caller, which I would expect must schedule the task in order to complete it . Also this is not the case, consider the following code:-

static async Task foo()
        {
            await bar();
        }

        static async Task bar()
        {
            print("bar");
        }

        static void Main(string[] args)
        {
            foo();

            Thread.Sleep(1000);
            return;
        }

bar will be executed without specifically scheduling the Task object created by foo.

So the questions are:

  1. Is it correct that ContinueWith not only creates the task but also schedules it .

  2. Is it correct that await not only creates the task for continuation as appears in the article but also schedule if possible (Call Post on SynchronizationContext or TaskScheduler).

  3. Why this design (scheduling and creation mixed) was adopted by async/await language designers?

Boris
  • 1,311
  • 13
  • 39
  • Your samples are completely synchronous - so all pieces are execucted synchronously. Using `async` keyword does not magically turn synchronous code to asynchronous. Use `Task.Delay(1000)` or similar to actually have async code. – Alexei Levenkov Jul 05 '14 at 20:14
  • @Alexei this doesn't matter if it synchronous or not. The question is why are they (cont & bar) are executed at all. You can turn it into asynchronous case easily but the question will stay the same. Who schedules the functions. – Boris Jul 05 '14 at 20:16
  • 1
    "schedules"? You call function (`foo()`) and it get executed - what so surprising about it? Maybe you want to read about how `async` is implemented - try this search http://www.bing.com/search?q=c%23+async+internals – Alexei Levenkov Jul 05 '14 at 20:20
  • @Boris, you still can schedule tasks created by `async` method, like [this](http://stackoverflow.com/a/24237840/1768303), although it's rarely needed. – noseratio Jul 07 '14 at 07:58

3 Answers3

4

You can specify the TaskScheduler to use in certain overloads of ContinueWith. You decide where to run that code. It is not true that the scheduler cannot be specified here.

An async method runs on the captured SynchronizationContext or on the current TaskScheduler after the first await point. So indeed an async method does schedule continuations. (I'm leaving out the fact that you can have custom awaiters.)

Your async example synchronously runs on the main thread to completion.

Is it correct that ContinueWith not only creates the task but also schedules it.

Yes, on the scheduler you specify.

Is it correct that await not only creates the task for continuation as appears in the article but also schedule if possible (Call Post on SynchronizationContext or TaskScheduler).

Yes, after the first await (that does not immediately complete) a scheduling operation happens.

Why this design (scheduling and creation mixed) was adopted by async/await language designers?

The internals of async/await are quite involved. There is a lot of machinery and non-trivial behavior under the hood. It is especially surprising and inconsistent on what thread the code of an async method will actually run. The designers of this feature apparently had a hard time making this work out of the box in all important scenarios. This leads to numerous questions on Stack Overflow every day about edge cases and very surprising behavior.

You can untangle creation and scheduling with the rarely-used Task.Start (TaskScheduler) method.

For continuations this model doesn't work out. When the antecendent completes the continuation must be activated. Without a TaskScheduler to do that the continuation cannot be run. That's why the scheduler must be specified at the time the continuation is being set up.

For async methods you can untangle creation and scheduling as well by using a custom awaiter. Or by using simpler models such as await Task.Factory.StartNew(..., myScheduler).

bar will be executed without specifically scheduling the Task object created by foo.

This task is not a CPU-based task. It is never scheduled. This is a task backed by a TaskCompletionSource. Specifying a scheduler doesn't make sense here.

usr
  • 168,620
  • 35
  • 240
  • 369
  • Thanks, I understood almost all of your answer :). However, can you elaborate (or give the link) explaining the last "...This task is not a CPU-based task. It is never scheduled..." sentence. – Boris Jul 05 '14 at 20:41
  • A task does not have to be associated with executable code. It can represent an arbitrary event. Look at the docs for TaskCompletionSource. Every async method returns a TaskCompletionSource.Task and internally calls TaskCompletionSource.SetXxx when it is done. (Of course, this fact is not detectable by callers. Implementation detail.) Hope that helps. If not, ask. – usr Jul 05 '14 at 20:43
  • 1
    Ok, thanks let me learn the subject so I would ask clever questions, meanwhile marking it as an answer. – Boris Jul 05 '14 at 20:45
  • @usr, +1 with nitpicking: there's no `Task.Run(..., myScheduler)` override, did you mean `Task.Factory.StartNew(..., myScheduler)`? – noseratio Jul 06 '14 at 02:07
  • @Noseratio thanks, fixed. That shows how often I use custom TaskScheduler's. – usr Jul 06 '14 at 09:37
1

Is it correct that ContinueWith not only creates the task but also schedules it .

ContinueWith will create the Task and use a TaskScheduler to execute the delegate provided to it. If no TaskScheduler is passed explicitly, it will use TaskScheduler.Current to execute the continuation.

Is it correct that await not only creates the task for continuation as appears in the article but also schedule if possible (Call Post on SynchronizationContext or TaskScheduler).

await does not create any Task. The await keyword is used on an awaitable type, such as a Task. When the compiler hits the await keyword, it lifts the method into a state machine, which is also responsible for capturing the current SynchronizationContext being used. Unless told not to (using ConfigureAwait(false)) it will marshal the continuation back onto that same context.

Why this design (scheduling and creation mixed) was adopted by async/await language designers?

In the case of await, the creation isn't done by the keyword, the creation is done by the awaitable method being called. In the case of Task.Run or Task.Factory.Startnew, the creation of the Task is being done by the method, and the user has to explicitly call ContinueWith in order to set the scheduling of the said continuation.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • When I said "await not only creates the task for continuation" I related to this article pseudo-code - http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx It can be seen there that await is translated into ContiueWith call – Boris Jul 05 '14 at 20:43
  • `await` will cause the rest of your method (hence your continuation) to be lifted into that state machine and to be executed one its done. To simply things it will do the `ContinueWith` for you without explicitly calling it. But in reality, things are more complicated behind the scenes – Yuval Itzchakov Jul 05 '14 at 20:47
0

The question arises from a misunderstanding of the async/await pattern.

aync does not schedule a method for asynchronous execution. It simply marks the use of the pattern, affecting the way the return value is handled by both the caller and the callee.

The async/await pattern was created to replace older patterns built around completion callbacks and thereby reduce the complexity of associated code.

Refer to: http://msdn.microsoft.com/en-us/library/hh191443.aspx

In that example, none of the code you see is being executed as an asynchronous task. Instead, the async and await are causing certain bits of the code to be reorganized into completion handlers (invisible to you), but effectively that is how it executes.

If you want to initiate an asynchronous action (launch and leave), you still have to use something like System.Threading: http://msdn.microsoft.com/en-us/library/a9fyxz7d(v=vs.110).aspx

or Task.Run: http://msdn.microsoft.com/en-us/library/system.threading.tasks.task_methods(v=vs.110).aspx

Zenilogix
  • 1,318
  • 1
  • 15
  • 31