Reputation: 580
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
Reputation: 116636
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.
Again the explanation is a bit simplistic. It refers to specific SynchronizationContext
s, the single threaded SynchronizationContext
s 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 SynchronizationContext
s 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
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:
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
This same cycle is executed for task2
.
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