0

If I have this

static void Main(string[] args)
{
    var tasks = new List<Task>();
    for (int i = 0; i < 10; i++)
    {
        tasks.Add(AsyncWithBlockingIO(i));
    }

    Task.WaitAll(tasks.ToArray());
}

private static async Task AsyncWithBlockingIO(int fileNum)
{
    var result = await File.ReadAllTextAsync($"File{fileNum}").ConfigureAwait(false);
    //will the below run concurrently on multiple threads?
    CpuNoIOWork(result);
}

Will CpuNoIOWork() run concurrently on multiple threads (using the thread pool) as the IO calls complete or will it only use 1 thread at a time?

3
  • 1
    You can add Debug.WriteLine(Thread.CurrentThread.ManagedThreadId); to CpuNoIOWork() to prove that the continuations are indeed all run concurrently. Commented Apr 15, 2021 at 13:54
  • 1
    You may want to take a look at this: Why File.ReadAllLinesAsync() blocks the UI thread? Not all Async-advertised methods are genuinely asynchronous. Commented Apr 15, 2021 at 16:47
  • 1
    @TheodorZoulias, thanks! not using the file APIs in the real code, but useful to know. Commented Apr 15, 2021 at 17:43

1 Answer 1

7

Will CpuNoIOWork() run concurrently on multiple threads (using the thread pool) as the IO calls complete or will it only use 1 thread at a time?

It will run concurrently, on thread pool threads.

However, depending on the internals of what you're awaiting, it might continue on an I/O thread pool thread, which is not desirable. If you have CPU-bound work, I recommend wrapping that in Task.Run, which will ensure the work will be run on a worker thread pool thread:

var result = await File.ReadAllTextAsync($"File{fileNum}").ConfigureAwait(false);
await Task.Run(() => CpuNoIOWork(result));
Sign up to request clarification or add additional context in comments.

5 Comments

Instead of await Task.Run in this specific case, would await Task.Yield() force the continuation to run on a threadpool thread?
@JohanP: Yes, but I prefer Task.Run so that the code is more explicit.
@JohanP the Task.Yield is a poor man's substitute of the non-existing API TaskScheduler.SwitchTo. The behavior of the Task.Yield depends on the ambient SynchronizationContext.Current (or the ambient TaskScheduler.Current if the context is null), so it's not a reliable (platform agnostic) way to switch to the ThreadPool, if that's your intention.
@TheodorZoulias Would you say in .net core that it is a reliable way to get to threadpool thread in this specific instance?
@JohanP no, I wouldn't. It's not the right tool for the job IMHO. If I really wanted to avoid the Task.Run for some reason, I would use a custom awaiter like noseratio's TaskSchedulerAwaiter, or a simplified version of it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.