7

I've read this thread which claims with reference to msdn with idea that async/await doesn't create new threads. Please look at following code:

static class Program
{
    static void Main(string[] args)
    {
        var task = SlowThreadAsync();
        for(int i = 0; i < 5; i++)
        {
            Console.WriteLine(i * i);
        }
        Console.WriteLine("Slow thread result {0}", task.Result);
        Console.WriteLine("Main finished on thread {0}", Thread.CurrentThread.ManagedThreadId);
        Console.ReadKey();
    }

    static async Task<int> SlowThreadAsync()
    {
        Console.WriteLine("SlowThreadAsync started on thread {0}", Thread.CurrentThread.ManagedThreadId);
        await Task.Delay(2000);
        Console.WriteLine("SlowThreadAsync completed on thread {0}", Thread.CurrentThread.ManagedThreadId);
        return 3443;

    }
}

As result of this code I got different ThreadId. Why the same thread gets different ThreadId?

different threads

Community
  • 1
  • 1
Yuriy Zaletskyy
  • 4,983
  • 5
  • 34
  • 54

3 Answers3

16

You're using a console application for your example. This effects greatly the outcome of your test. A console application has no custom SynchronizationContext (like Winforms, WPF and ASP.NET have), hence it uses the ThreadPoolTaskScheduler to schedule continuations on an arbitrary thread-pool thread. Try this same example in a UI application and you'll see the continuation invoked on the same thread.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • I don't think `ThreadPoolTaskScheduler` is involved here. In the absence of s.context, the continuation simply takes place on whatever thread the underlying `TimerCallback` gets called (scheduled via `ThreadPool.UnsafeQueueUserWorkItem`). – noseratio Sep 08 '15 at 22:33
  • I disagree, with one small but important difference. In both WinForms and WPF you will receive a deadlock from `Task.Result`. – Aron Oct 01 '16 at 17:49
  • @Aron You disagree with which part? :) – Yuval Itzchakov Oct 01 '16 at 17:50
  • @YuvalItzchakov, the continuation would NEVER run on the UI application, because it is busy blocked waiting for the continuation to complete on `Task.Result` :) – Aron Oct 01 '16 at 17:52
  • @Noseratio I'm afraid you are mistaken. By default, most continuations are scheduled using the `SynchronizationContext.Current` rather than "Current Thread". It so happens that in the case of WinForms and WPF, SynchronizationContext.Current is set to something that represents the UI Thread's Message pump. – Aron Oct 01 '16 at 17:55
  • @Aron, what I said is correct *for this particular code sample*, where there is no s. context. Besides, `ThreadPoolTaskScheduler.TryExecuteTaskInline` still might be used here (possibly - not sure - feel free to verify) to synchronously execute the `await` continuation. Regardless, the continuation here will be executed *on the very same thread where `Task.Delay` has completed*. Not sure why you brought all the `SynchronizationContext` talks here. – noseratio Oct 01 '16 at 22:58
  • @YuvalItzchakov, if I use `ConfigureAwait(false)` to avoid to capture the context in a WPF appl or a Web Appl, would be the same result of the console app, scheduling the continuations in a arbitrary thread-pool thread? – ocuenca Jun 26 '18 at 14:14
  • 1
    @octavioccl Yes. If no context is captured, it is scheduled on the thread pool. – Yuval Itzchakov Jun 26 '18 at 14:24
4

What the articles you linked are trying to get across is that calling async methods does not guarantee that any of the code runs on a separate thread. So if you do want to guarantee this, you have to do it manually. In particular they're not trying to say that an async method will always run on the same thread as the calling method, because this is blatantly false in many scenarios.

Joren
  • 14,472
  • 3
  • 50
  • 54
1

I know its been a bit since the question was asked, but I ran into this problem and have come up with more details on why/when this happens. await works by scheduling the code following the await to be run after the awaited task finishes, on whatever thread the threadpool finds convenient. Oftentimes this seems to be on the same thread as the awaited task. Not sure how this plays with SynchronizationContext mentioned in other answers.

I have noticed an exception is when the awaited task finished quickly, it seems like their isn't enough time to place the code on the callback, so the code ends up being called on a third thread.

yesennes
  • 1,147
  • 1
  • 10
  • 19