10

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;
        }

enter image description here

Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • Link to the article you are confused about – Dale Wilson Jul 15 '13 at 18:39
  • 1
    The Task you await runs on another thread. All other code in the async method runs on the original thread. Await does NOT start a new task. It's equivalent is a ContinueWith in the ORIGINAL thread. There are multiple blogs that discuss this. – Panagiotis Kanavos Jul 15 '13 at 18:55
  • @PanagiotisKanavos my examples just shows you it doesnt. 17 is not 7. when the task is back , a differnt thread serve it. – Royi Namir Jul 15 '13 at 18:56
  • @RoyiNamir you misunderstand what your example shows. In a console application there IS NO current Synchronization context to which `await` can return. Try the same in a WinForms application. Stephen Cleary already answered you and there will be multiple other answers that will say the same thing. – Panagiotis Kanavos Jul 15 '13 at 19:00
  • @PanagiotisKanavos it doesn't matter. I run this with asp,net and still it is different thread. Please read stephen answer. the whole issue here is wording. Context vs thread. – Royi Namir Jul 15 '13 at 19:02
  • He just edited it saying that in a UI context you end up in the single UI thread. It is definitely NOT wording – Panagiotis Kanavos Jul 15 '13 at 19:02
  • @PanagiotisKanavos thank you. you were right. *i just needed a little bit of explanation). – Royi Namir Jul 15 '13 at 19:38

3 Answers3

27

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    Eric , but if I left my calculations and went feeding the cat - the calculation operation ( which i do with pencil and paper) is blocked --until i come back.... so it sounds as synchronous operation. please correct me. – Royi Namir Jul 18 '13 at 08:04
  • 12
    That's not the asynchronous operation. Performing the lengthy calculation is the asynchronous operation because you're not blocked from doing other work during it. – Eric Lippert Jul 18 '13 at 14:15
  • @EricLippert Is your comment above prerequisite upon there being more than one processor or if there be only one processor, the processor has the ability to work on more than one task at a time? If only one task can be worked on at a time, then the computer running the code would not be able to work on the asynchronous "lengthy calculation" and the "other work" work at the same time. If the current task is the "lengthy calculation", then the computer would have to wait to do the "other work". I interpret that as being blocked from other work. Help me. Am I using "thread" and "task" correctly? – daniel Dec 01 '21 at 22:42
  • 1
    @DanielBrower: I just deleted a comment in reply because I realized that in fact the confusion is because historically I've use the word "blocked" to mean two things: (1) a thread is put to sleep until a signal is received, and (2) a workflow synchronously waits until a call completes. Amazing that I've had this bad pedagogic practice for over a decade and never really thought about it. I will give some thought as to how to better explain this, thanks! – Eric Lippert Dec 23 '21 at 17:11
14

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • ive read your article many times. ( look at my last async questions ;) – Royi Namir Jul 15 '13 at 18:58
  • Yet the article answers the question. :) Read the section about `SynchronizationContext` and `TaskScheduler` again. – Stephen Cleary Jul 15 '13 at 18:59
  • So it is not the Thread which is kept. ( my actual question - thanks for answering) - but the context ( which is already known to me.)... ? please confirm – Royi Namir Jul 15 '13 at 18:59
  • 2
    `async` doesn't really care about threads. It cares about the "context", which is `SynchronizationContext.Current` (unless it is `null`, in which case it's the current `TaskScheduler`). – Stephen Cleary Jul 15 '13 at 19:00
  • Great. that's what i was thinking (and already knew that). but when somneone say THREAD - i thought it meant THREAD. (and ive tested it). words are important – Royi Namir Jul 15 '13 at 19:00
  • 2
    Note that in a Console application SynchronizationContext.Current is null, so you end up in a different thread. In a WinForms/WPF application you return to the UI Thread, in which case a THREAD means a THREAD – Panagiotis Kanavos Jul 15 '13 at 19:01
  • Technically, Eric doesn't actually say that the `async` method will always resume on the same thread; he just says "you stay on the current thread as much as possible". – Stephen Cleary Jul 15 '13 at 19:03
  • Youve already answer it [here](http://stackoverflow.com/questions/17630506/async-at-console-app-in-c-sharp) ( to my question ) – Royi Namir Jul 15 '13 at 19:03
  • @RoyiNamir: My blog post describes the ASP.NET behavior as well. You may resume on another thread, but you will have the same request context. – Stephen Cleary Jul 15 '13 at 19:32
14

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Slightly OT, but I stumbled across that bizarre force-input-to-be-synchronous behavior a bit ago. Do you know why they did this? – Stephen Cleary Jul 15 '13 at 19:05
  • 1
    No idea. There was a bigger plan behind it, the same lock was also added to the CRT. There are a *lot* of programmers that are nursing deadlock on their little "Hit any key to continue" test programs, adding this lock was a mistake. – Hans Passant Jul 15 '13 at 19:09
  • 3
    And was fixed by removing it. – Hans Passant Oct 22 '15 at 13:24