Is the creation of a Task
a context switching point, or only when it starts to await
or do other asynchronous things?
For example if I have these functions:
async Task Foo()
{
Console.WriteLine("In Foo");
await bar();
}
void CallFoo()
{
var task = Foo();
Console.WriteLine("Returned from Foo");
task.Wait();
}
Is it possible for "Returned from Foo" to print before "In Foo"?
No. It schedules the remaining code to run on the correct context. That context might be a threadpool thread. It might be the UI thread.
You can't directly switch a process from the running state to the ready state. You have to save the context of that process. If you are not saving the context of any process P then after some time, when the process P comes in the CPU for execution again, then the process will start executing from starting.
A context-switch must occur when one thread is a candidate for CPU time and no CPUs are currently available (in use by another thread). A context switch also occurs when you call Thread.
Each context switch takes the kernel about 5 μs (on average) to process. However, the resulting Cache misses add additional execution time that is difficult to quantify. The more frequent the context switches, the more your CPU utilization degrades.
Is it possible for "Returned from Foo" to print before "In Foo"?
In the program as you've written it, no, that is not possible.
If you have a different program that runs a worker thread, then you have all the usual issues of ordering side effects between threads. There is nothing special about "async/await" that makes those issues go away.
Is the creation of a Task a context switching point, or only when it starts to await or do other asynchronous things?
Let's be precise by what we mean by a "context switching point".
A method in C# can do one of four things:
A method marked async
can suspend, but they only suspend when they await
a non-completed awaitable. Awaiting a completed awaitable does not suspend, but it can throw.
Tasks returned by async
methods are hot. That is, the asynchronous workflow begins and it runs until it returns, throws or suspends. It is possible to create a "cold" task with the Task
constructor, and that doesn't start until you call Start
on it. Normally you would not do that unless you were writing your own task scheduler.
Note that this is different than iterator blocks. A common mistake is:
IEnumerable<int> GetMeSomeInts(int x)
{
if (x < 0)
throw new SomeException();
for (int i = 0; i < x; i += i)
yield return i;
}
If you say
var nums = GetMeSomeInts(-1); // 1
foreach(int num in nums) // 2
...
Then the throw does not happen on line 1, it happens on line 2! Iterator blocks do not run up to the first yield
when you call them. They suspend immediately and do not execute until iterated in the foreach
. Be careful, particularly if you are working with both async and iterator coroutines in the same program.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With