Anders
Anders

Reputation: 580

Asynchrony and Synchronization Contexts

Consider the following code:

async Task Go()
{
   var task1 = PrintAnswerToLife();
   var task2 = PrintAnswerToLife();
   await task1; await task2;
}

async Task PrintAnswerToLife()
{
   var task = GetAnswerToLife();
   int answer = await task; Console.WriteLine(answer);
}

async Task<int> GetAnswerToLife()
{
   var task = Task.Delay(5000);
   await task; int answer = 21 * 2; return answer
}

Question 1:

In chapter 14, page 588, of the book "C# 5.0 in a Nutshell" by the Albahari brothers, it is stated that the two asynchronous operations task1 and task2 run in parallel. That does not seem correct to me. As far as I understand, when var task1 = PrintAnswerToLife(); runs, the executions goes into PrintAnswerToLife() where when it hits await returns execution to Go(), and proceeds to the next line var task1 = PrintAnswerToLife();, where the same thing happens again. In other words there is nothing occurring in parallel in the first two lines of Go(). In fact, unless there is a thread involved (as in Task.Run() or Task.Delay()), no real parallelism ever occurs. Have I understood this correctly? If so, what does Albahari really mean by saying that the two operations run in parallel?

Question 2:

On the same page, Albahari goes on to state the following:

Concurrency created in this manner occurs whether or not the operations are initiated on a UI thread, although there is a difference in how it occurs. In both cases, we get the same concurrency occurring in the bottom-level operations that initiate it (such as Task.Delay, or code farmed to Task.Run). Methods above this in the call stack will be subject to true concurrency only if the operation was initiated without a synchronization context present...

What does Albahari mean by this? I do not understand how a SynchronizationContext comes into play here, and what difference it makes.

Upvotes: 3

Views: 245

Answers (2)

i3arnon
i3arnon

Reputation: 116636

Question 1:

Parallel usually means multiple threads processing simultaneously. The more accurate term to use here is concurrent. In your example both tasks are executed concurrently. That means that the asynchronous operation (i.e. await Task.Delay(5000)) in both tasks is "executed" at the same time. That's why both tasks would complete about 5 seconds since Go began. If the tasks would have been run sequentially it would have taken 10 seconds. The synchronous continuations would then be scheduled on a thread pool thread (assuming there's no special SynchronizationContext) and there there's a chance for parallelism.

Have I understood this correctly?

Yes.

If so, what does Albahari really mean by saying that the two operations run in parallel?

That they run concurrently and asynchronously with potentially parallel continuations.

Question 2:

Again the explanation is a bit simplistic. It refers to specific SynchronizationContexts, the single threaded SynchronizationContexts used in GUI environments. Because this SynchronizationContext schedules all work on a single specific thread it doesn't allow for "true concurrency". However there are other, multithreaded SynchronizationContexts and you can create your own. Therefore using a SynchronizationContext doesn't necessarily hinder concurrency (and you can also disable the SynchronizationContext capture altogether by using ConfigureAwait(false) on a task)

What does Albahari mean by this?

In GUI environments the SynchronizationContext uses a single thread that can't execute anything in parallel.

Upvotes: 2

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149608

In fact, unless there is a thread involved (as in Task.Run() or Task.Delay()), no real parallelism ever occurs. Have I understood this correctly?

A thread has to be involved only if we're talking about parallelism. Your example is about concurrency

Lets break it down:

  1. You execute PrintAnswerToLife, which in turn runs GetAnswerToLife, and right there it hits its first await on Task.Delay(5000). Once hit, return will yield control back to PrintAnswerToLife, which will then itself await the Task<int> returned, which will cause execution to yield back to Go. Meanwhile, start the execution of your second call to PrintAnswerToLife

  2. This same cycle is executed for task2.

  3. You then sequentially await each Task. You could easily await on them concurrently using Task.WhenAll.

What does Albahari mean by this? I do not understand how a SynchronizationContext comes into play here, and what difference it make?

A SynchronizationContext is in charge of your execution flow. In .NET we have various SynchronizationContext's, such as the DispatcherSynchronizationContext and the WinFormSynchronizationContext, which are responsible for marshaling work back onto the UI thread (WPF and WinForms, respectively). I think what he's trying to point out is the fact that each of those SynchronizationContext will eventually marshal control back to some sort of UI message loop, which will be forced to execute synchronously, one after the other. If there isn't a SynchronizationContext, the default used is the ThreadPoolSynchronizationContext, which will invoke the continuation of those tasks on an arbitary threadpool thread. This isn't entirely true, since one can avoid the marshaling of the context back to the UI thread using ConfigureAwait(false).

Upvotes: 4

Related Questions