问题
I have a bunch of work to do (either CPU-bound or IO-bound with no async interface to use) within a Task. I'm wondering if it's OK to just do all the non-async work within the task like this:
async Task DoSomeStuff()
{
await SomethingAsync();
…
DoCpuBoundWork();
…
await SomethingElseAsync();
}
or should I use Task.Run like this?
async Task DoSomeStuff()
{
await SomethingAsync();
…
await Task.Run(() => DoCpuBoundWork());
…
await SomethingElseAsync();
}
I know tasks aren't necessarily executed on another thread, so I'm wondering if the scheduler might make assumptions about tasks being non-blocking that'll make doing CPU-bound work outside of Task.Run slow the application down. For example, if the scheduler decided to schedule the CPU-bound work to the app's UI thread, there could be a slow-down.
I'm certain this question's been asked before, but I can't find the right keywords to search for it, so sorry about that.
回答1:
As noted in this page (section Deeper Dive into Task and Task<T> for a CPU-Bound Operation), tasks run on the thread from which they're called, and CPU-bound work should indeed be wrapped in a Task.Run
so it'll run on another (background) thread. So yes, it's OK and normal to use Task.Run within an async method for CPU-bound or otherwise blocking work. Quoted from the page:
public async Task<int> CalculateResult(InputData data)
{
// This queues up the work on the threadpool.
var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));
// Note that at this point, you can do some other work concurrently,
// as CalculateResult() is still executing!
// Execution of CalculateResult is yielded here!
var result = await expensiveResultTask;
return result;
}
CalculateResult() executes on the thread it was called on. When it calls Task.Run, it queues the expensive CPU-bound operation, DoExpensiveCalculation(), on the thread pool and receives a Task handle. DoExpensiveCalculation() is eventually run concurrently on the next available thread, likely on another CPU core. It's possible to do concurrent work while DoExpensiveCalculation() is busy on another thread, because the thread which called CalculateResult() is still executing.
Once await is encountered, the execution of CalculateResult() is yielded to its caller, allowing other work to be done with the current thread while DoExpensiveCalculation() is churning out a result. Once it has finished, the result is queued up to run on the main thread. Eventually, the main thread will return to executing CalculateResult(), at which point it will have the result of DoExpensiveCalculation().
回答2:
The answer is: it depends.
You pointed out the scheduler, that's exactly the problem. If you're running on the threadpool (default scheduler), then running synchronous CPU-bound work is perfectly fine. If you're running on the UI thread, this may lead to a poor user experience.
That said, while this is a problem, this is not your problem. Your contract, by implemented DoSomeStuff
, is that you'll run some work on the current scheduler. It's up to the caller to figure out whether that could be an issue, and in that case add a Task.Run
to offset the call to another scheduler.
In a nutshell, don't use await Task.Run(() => DoCpuBoundWork());
. Let the caller, who knows the context of execution, decide for you.
来源:https://stackoverflow.com/questions/52234632/should-one-use-task-run-within-another-task