async - stay on the current thread?
I've read Eric lippert's article about async
, and about confusions people had with async
keyword. he said :
it (
async
) means “this method contains control flow that involves awaiting asynchronous operations and will therefore be rewritten by the compiler into continuation passing style to ensure that the asynchronous operations can resume this method at the right spot.” The whole point of async methods it that you stay on the current thread as much as possible
I don't understand this. If I execute an asynchronous method (Task
) and it runs , it surely runs on another thread.
Moreover , If I write a method uses await
, (imho) it releases the normal control flow , and code is refactored alike "ContinueWith
" later , on another thread.
I tested it with (console) :
/*1*/ public void StartChain()
/*2*/ {
/*3*/ var a = FuncA();
/*4*/ Console.WriteLine(a.Result);
/*5*/ }
/*6*/
/*7*/ public async Task < int > FuncA()
/*8*/ {
/*9*/ Console.WriteLine("A--" + Thread.CurrentThread.ManagedThreadId);
/*10*/ var t = await FuncB();
/*11*/ Console.WriteLine("B--" + Thread.CurrentThread.ManagedThreadId);
/*12*/ return t;
/*13*/ }
/*14*/
/*15*/ public async Task < int > FuncB()
/*16*/ {
/*17*/ Console.WriteLine("C--" + Thread.CurrentThread.ManagedThreadId);
/*18*/ await Task.Delay(2000);
/*19*/ Console.WriteLine("D--" + Thread.CurrentThread.ManagedThreadId);
/*20*/ return 999;
/*21*/ }
/*22*/
/*23*/ void Main()
/*24*/ {
/*25*/ StartChain();
/*26*/ }
/*27*/
the result is :
A--7
C--7
D--17 <-----D and B are on different thread
B--17
999
So what did Eric mean by saying "stay on the current thread"?
edit 1:
in asp.net
it also return differnt thread ID.
public async Task<int> FuncA()
{
Response.Write("<br/>C----" + Thread.CurrentThread.ManagedThreadId);
var t = await FuncB();
Response.Write("<br/>D----" + Thread.CurrentThread.ManagedThreadId);
return t;
}
public async Task<int> FuncB()
{
Response.Write("<br/>E----" + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(2000);
Response.Write("<br/>F----" + Thread.CurrentThread.ManagedThreadId);
return 999;
}
protected async void Page_Load(object sender, EventArgs e)
{
Response.Write("<br/>A----" + Thread.CurrentThread.ManagedThreadId);
var a=await FuncA();
Response.Write("<br/>B----" + Thread.CurrentThread.ManagedThreadId);
}
A----8
C----8
E----8
F----9
D----9
B----9
edit 2
(after getting an answer)
it seems that thread is served only at GUI apps :. I run this code at winform
public async Task<int> FuncA()
{
textBox1.Text +=Environment.NewLine+ "\nC----" + Thread.CurrentThread.ManagedThreadId;
var t = await FuncB();
textBox1.Text += Environment.NewLine + "\nD----" + Thread.CurrentThread.ManagedThreadId;
return t;
}
public async Task<int> FuncB()
{
textBox1.Text += Environment.NewLine + "\nE----" + Thread.CurrentThread.ManagedThreadId;
await Task.Delay(2000);
textBox1.Text += Environment.NewLine + "\nF----" + Thread.CurrentThread.ManagedThreadId;
return 999;
}
private async void Form1_Load(object sender, EventArgs e)
{
textBox1.Text += Environment.NewLine + "\nA----" + Thread.CurrentThread.ManagedThreadId;
var a = await FuncA();
textBox1.Text += Environment.NewLine + "\nB----" + Thread.CurrentThread.ManagedThreadId;
}
If I execute an asynchronous method and it runs, it surely runs on another thread.
No, it typically runs on another thread. It does not surely run on another thread.
Stop thinking about threads for a moment and think about the nature of asynchrony. The nature of asynchrony is:
- I've got some workflow that I am currently executing.
- I can't proceed in this workflow until I get information X.
- I'm going to do something else until I get information X.
- At some point in the future, once I have X, I'm going to come back to where I left off in my workflow and continue.
Suppose you're doing your taxes and in the middle of this complicated workflow you have a large addition to perform. You can perform a few operations then remember where you are, and go have lunch. Then come back and perform a few more operations, then remember where you are, and feed the cat. Then come back and perform a few more operations, then remember where you are, and wash the dishes. Then finish off the calculations, and resume where you left off in your workflow.
That's an asynchronous calculation but it only needed a single worker to do it. Having multiple workers is just a particularly convenient way to do asynchrony, it is not a requirement.
The async/await support was added to help programmers write GUIs that don't freeze. Particularly useful in Store apps, and the core reason it was added to C# v5, WinRT is a pretty unfriendly api that has many asynchronous methods.
The "stay on the same thread" scenario is very important in a GUI app, required because a GUI isn't thread-safe. It does however require a dispatcher loop (aka Application.Run), the only way to get asynchronous code to resume on the same thread. That loop is the core solution to the producer-consumer problem.
Clearly your program doesn't have one, looks a lot like a console mode app. You therefore don't get this behavior, it resumes on a worker thread.
Not much of a problem, you don't actually need it to resume on the same thread since a console is thread-safe anyway. Well, mostly, not counting the lock that was added in .NET 4.5 when you ask for input. Which of course also means that you don't have a heckofalot of use for async/await either, a Task works fine as well.
Eric Lippert's "thread" terminology is simplified. I have an async
/await
intro on my blog that explains how await
will capture the current context and use that to resume the async
method.
If you are in a UI context, then the context is the single UI thread, and the async
method will resume on that thread. Otherwise, the rules are a bit more complicated. In particular, Console apps do not provide any context, so async
methods by default resume on the thread pool.