1

Suppose an ASP.NET WebAPI request arrives at a controller method.

Suppose the request represents an 'event' which needs processed. The event has multiple operations associated with it that should be performed in parallel. For example, each operation may need to call out to a particular REST endpoint on other servers, which are I/O bound operations that should get started as soon as possible and should not wait for one to return before starting the next one.

What is the most correct/performant way to implement this pattern?

I've read that using Task.Run is a bad idea, because it just grabs additional ThreadPool threads, leaving the main request thread idle/blocked. While that makes sense if I was running a single task, I'm not sure that advice applies in this case.

For example, if the event has 4 operations that needed completed (each having possibly multiple I/O bound calls of their own), I would call Task.Run in a loop 4 times to initialize each operation, then wait on the resulting tasks with Task.WaitAll.

Q1: Would the main request thread be returned to the ThreadPool for use by another request while waiting for Task.WaitAll to return, or would it just hog the main thread leaving it idle until Task.WaitAll completes?

Q2: If it hogs the main thread, could that be resolved by marking the controller method with the async keyword, and using an await Task.WhenAll call instead? I'd imaging that this would return the main thread to the pool while waiting, allowing it to be used for other requests or event operations.

Q3: Since Task.Run queues up a work item that could be blocked on an I/O bound call, would performance improve if the operations were all implemented with async and used await calls on Task-based asynchronous I/O methods?

Regarding the whole approach of using Task.Run for the event's operations, the goal is just get all of the operation's I/O bound calls started as soon as possible. I suppose if (as in Q3) all operations were async methods, I could just get them all started on the main request thread in a loop, but I'm not sure that would be better than starting them with separate Task.Run calls. Maybe there's a completely different approach that I'm unaware of.

Triynko
  • 18,766
  • 21
  • 107
  • 173
  • "I've read that using Task.Run is a bad idea, because it just grabs additional ThreadPool threads, leaving the main request thread idle/blocked.". Show me where you've read that. Also, all of your questions can be answered by actually learning basics of tpl and async/await. – FCin Jun 05 '18 at 04:49
  • That's actually a fact that the main thread is blocked, and that using Task.Run in an ASP.NET web app is a bad idea that just causes more thread pool threads to be tied up. Also, when you consider "quirks mode" in asp.net 4.5, async and await patterns are not straightforward, considering how the ExecutionContext, CallContext, and SynchronizationContexts are continued across awaits, not to mention settings that control whether those or the HttpContext are continued across awaits. https://stackoverflow.com/questions/33764366/is-task-run-considered-bad-practice-in-an-asp-net-mvc-web-application – Triynko Jun 05 '18 at 04:55
  • Also: "By using Task.WaitAll in the request's context you're blocking it, which is preventing it from being used to handle the continuations from all of the other tasks." https://stackoverflow.com/a/12981585/88409 "Also note that one of the primary benefits of async/await in an ASP app is to not block the thread pool thread that you're using to handle the request. If you use a Task.WaitAll you're defeating that purpose." – Triynko Jun 05 '18 at 05:03
  • Of course `Task.WaitAll` blocks thread, but `Task.Run` does not block it in itself, only throws it on a queue and captures context. If you need to run multiple tasks then just call all of them in asynchronous event handler, return task from each of them and await `Task.WhenAll`. Main thread is still processing, other threads are doing I/O and you keep the flow. – FCin Jun 05 '18 at 05:10
  • It sounded like you were questioning whether Task.Run+Task.WaitAll would block the main thread. That was my Q1, but I know the answer is, it does, which is why I asked Q2. Q3 is... given operations tied to methods with signatures like `async List Op1()`, `async List Op2()`, etc. Would it be better to start them like this: `var o1 = Op1(); o1.Start(); var o2 = Op2(); o2.Start(); await Task.WhenAll(o1, o2);` (i.e. in sequence), or like this: `await Task.WhenAll(Task.Run(async () => await Op1()), Task.Run(async () => await Op2()));` (i.e. queue them to run concurrently). – Triynko Jun 05 '18 at 05:30
  • Some really strange behavior is described here: http://vegetarianprogrammer.blogspot.com/2012/12/understanding-synchronizationcontext-in.html – Triynko Jun 05 '18 at 05:37

0 Answers0