2
  • When I call test1() method then ManagedThreadId will be changed after Delay(1).
  • When I call test2() method (instead of test1()) then ManagedThreadId will stay same.

When async-await change thread?

In test2() method time required to finish method is even longer then in test1()

    [Route("api/[controller]")]
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        await MainAsync();
        return new ObjectResult(null);
    }

    static async Task MainAsync()
    {
        Console.WriteLine("Main Async: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
        await test1();
        //await test2();
        // . . .more code
    }

    private static async Task test1()
    {
        Console.WriteLine("thisIsAsyncStart: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
        await Task.Delay(1);
        Console.WriteLine("thisIsAsyncEnd: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
    }

    private static async Task test2()
    {
        Console.WriteLine("thisIsAsyncStart: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
        System.Threading.Thread.Sleep(5000);
        await Task.FromResult(0);
        Console.WriteLine("thisIsAsyncEnd: " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
    }
Raskolnikov
  • 3,791
  • 9
  • 43
  • 88
  • 1
    I suggest you read the answers to [How different is await/async from threading?](http://stackoverflow.com/q/13429294/215552)... – Heretic Monkey Apr 19 '17 at 14:20
  • 2
    I have an [async intro](https://blog.stephencleary.com/2012/02/async-and-await.html) that should help you. – Stephen Cleary Apr 19 '17 at 14:44
  • 1
    Suppose you put a chicken to roast in the oven and then you pay some bills while you're waiting. **When did putting the chicken in the oven cause you to hire a secretary to pay your bills?** It didn't. Asynchrony is not about multiple worker threads. It's about breaking up tasks into smaller pieces so that you can re-order them in time. – Eric Lippert Apr 19 '17 at 14:47

1 Answers1

9

test1 awaits Task.Delay(1), which isn't going to be completed at the time it goes to await it, meaning the rest of test1 needs to be scheduled as a continuation.

For test2 you're awaiting Task.FromResult, which will always return an already completed Task. When you await an already completed task the method can just keep running on the current thread, without needing to schedule a continuation.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Ok. But why new thread is not created in the moment when I call await test1(); (or await test2())? This Methods are not complied and it is waiting for some time. But when I call await "Task.Delay(1);" method than thread is changed. – Raskolnikov Apr 19 '17 at 14:43
  • 3
    @Raskolnikov `test1` actually calls an asynchronous operation that isn't already completed, so it itself will end up returning before it has completed, meaning that, when awaited, the caller will also return and then execute the rest of the method on the current synchronization context. `test2` never ends up awaiting any non-completed `Task` instances, so it will end up running synchronously, instead of returning an incomplete `Task`, meaning when you `await` *it* it just runs synchronously and unwraps the `Task`. – Servy Apr 19 '17 at 14:46
  • 1
    I still struggle with this. What is different between await test1(); and await Task.Delay(1);. Both are methods, both are static and both return Task. Why consider waiting on await Task.Delay(1); but does not consider waiting on await test1(); ? Method test1() is long running process same as method Task.Delay(1) ? – Raskolnikov Apr 20 '17 at 08:59
  • @Raskolnikov It's just as I said right at the start of my answer, what matters is whether or not the `Task` that's returned is already completed or not. If it's already completed, it'll just continue on synchronously. – Servy Apr 20 '17 at 13:09
  • So in other words, while `async/await` doesn't inherently spawn a new thread, awaiting an incomplete `Task` will cause the executing thread ID to change for the caller upon task completion. In my case, I created a WPF form object in my `async` method but after running `await Task.Delay(500)` I'm suddenly on a new thread that doesn't own the object causing an `InvalidOperationException`. – Mike Lowery Sep 12 '20 at 01:36
  • @MikeLowery `await` will schedule the continuation to run in the current synchronization context (unless configured not to), and in contexts such as WPF the UI thread will set the current sync context to be one that schedules work to the UI thread. Now if you've configured the await to run in the default context instead, then it'll run in a thread pool thread. – Servy Sep 12 '20 at 17:43
  • I will speculate that the confusion partially comes from the following. Here `MainAsync()` awaits `test1()`, and `test1` awaits `Task.Delay()`. The inner `await` is incomplete, so the thread changes (because that is what this synchronization context does). But the outer `await` (the `await test1()`) is also incomplete, does the thread also change there, two thread changes in total? Or does the thread change affect the entire chain of awaits as a whole? Or is it an implementation detail? (Inspired by https://stackoverflow.com/q/69131559/11683.) – GSerg Sep 10 '21 at 12:07