1

Having read a /lot/ of documentation on the async await pattern I thought I had a pretty good handle on the pattern. I read all about async all the way then read another article that mentions 'if it runs in under 50 milliseconds don't async it'. It seems there is conflicting information and/or opinions and I have managed to just confuse myself. I have also read that Task.Yield() will force an async decorated method to run asynchronously.

Given the following code:

    static async Task MethodA() {
        await MethodB().ConfigureAwait(false);
    }

    static async Task MethodB() {
        await Task.Yield();
        MethodC();
    }

    static void MethodC() {
        // do some synchronous stuff
    }

    static async Task Main(string[] args) {
        var task1 = Task.Run(() => MethodA().ConfigureAwait(false));
        var task2 = Task.Run(() => MethodB().ConfigureAwait(false));

        await Task.WhenAll(new List<Task>() { task1, task2 });
    }

Will MethodC run synchronously or asynchronously, I assumed asynchronously as it was called from an asynchronous method. Also, is Task.Yield necessary at all?

To be honest this is doing my head in, every article I have read delves deeper and deeper into the async await pattern with the whys and wherefores and is just adding more complexity to the question. Just looking for a simple answer.

Muz
  • 289
  • 1
  • 3
  • 11
  • "I have also read that Task.Yield() will force an async decorated method to run asynchronously" please, show me where you read that. – Ian Kemp Nov 08 '21 at 09:21
  • 2
    `Task.Yield` is useful for the purpose of breaking up an async operation that might otherwise block the *caller's* thread, i.e. you want to defer it and/or pass it to the thread-pool; since `MethodB()` is called via `Task.Run`, it is *already on the thread-pool*, and thus using `Task.Yield` does nothing useful. It is also relevant to note that async and concurrency are very different concepts; your `task1`/`task2` setup demonstrates concurrency, not really `async` - since there isn't really **anything truly `async` happening here** – Marc Gravell Nov 08 '21 at 09:22
  • 1
    @IanKemp that much is at least true; by definition, `Task.Yield` returns something that reports `false` for `IsCompleted`, and will typically be reactivated via the thread-pool, so: from that perspective: yes, it will take a method that would otherwise run synchronously (meaning: it would return an already-completed awaitable), instead return an incomplete awaitable, which means: it would be classed as fully async. Note that this doesn't necessary mean what OP might mean by the terms. – Marc Gravell Nov 08 '21 at 09:25
  • Simpler to just accept that if you call a method that returns a `Task`, that task may or may not be completed when the method call returns, and you need to use the `Task` to determine when it's complete. *How* the method is implemented, how it may or may not arrange for the `Task` to complete later are largely *irrelevant* from the calling code's perspective. – Damien_The_Unbeliever Nov 08 '21 at 09:33
  • @MarcGravell Yes, it's of course true, it's just completely without context - hence why I'm asking for the blog post/article/whatever where this fact was (supposedly) stated, to get at the missing context that the asker has ignored. – Ian Kemp Nov 08 '21 at 09:37
  • 1
    @IanKemp Task.Yield() https://stackoverflow.com/questions/22645024/when-would-i-use-task-yield – Muz Nov 08 '21 at 09:55

2 Answers2

2

Will MethodC run synchronously or asynchronously

Synchronously. It has a synchronous signature, so it will always run synchronously.

I assumed asynchronously as it was called from an asynchronous method

The context from which it is called is irrelevant to how MethodC will run.

is Task.Yield necessary at all

Well it will force MethodB to yield a Task before MethodC runs, but this will be incomplete until MethodC finishes, and because MethodC is synchronous (does not release the thread) it achieves nothing useful.

Just looking for a simple answer

async bubbles up the call stack, and because you are not consuming an async method here (MethodC is synchronous) you should not be using async at all.

Johnathan Barclay
  • 18,599
  • 1
  • 22
  • 35
0

This answer contains general info and advices, and it's not focused on the code that you posted.

  1. Being confused while learning async-await is OK. Getting a perfect understanding of async-await without going through a confusion phase is practically almost impossible IMHO.
  2. The await Task.Yield(), await Task.Delay(1), await Task.Run(() => { }), and .ConfigureAwait(false) are dirty hacks that people sometimes use out of frustration that an explicit way to switch imperatively to the ThreadPool context does not exist (in the standard libraries). A SwitchTo method existed in some pre-prelease .NET version, then it was removed for technical reasons, then the technical reasons were eliminated, reintroducing the method never became a high enough priority, and so it didn't happen. It may happen in the future, but don't hold your breath. It is currently available in the Microsoft.VisualStudio.Threading package, if you want it.
  3. You don't need any of these hacks to write async-await code successfully. If you want to offload work to the ThreadPool, there is the Task.Run method that does the job perfectly.
  4. Offloading work to the ThreadPool should be done at the "application code" level, not at the "library code" level. You can read this article to understand why exposing asynchronous wrappers for synchronous methods is not a good idea.
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 2
    I believe `SwitchTo` was removed for usability reasons, not purely technical ones. And the usability reason still exists: specifically, in a method with a `SwitchTo`, which context does the `catch` or `finally` blocks run in? It could be either context, which is why switching contexts in the same method is not recommended. Sure, now it's possible to switch back (the technical reason is eliminated), but it's still a mental burden to remember that you even have to do that (the usability reason remains). – Stephen Cleary Nov 14 '21 at 21:12