对多个异步和假异步方法使用 Task.WhenAll

我的一位同事重构了我们的控制器方法,以便我们所有的 IO 操作,包括同步操作,都封装在单独的任务中,然后所有这些任务都通过Task.WhenAll. 我完全可以理解这个想法:我们使用更多线程,但我们所有的 IO 操作(我们可以有很多)都以最慢的速度执行,但我仍然不确定这是否是正确的路径. 这是一种有效的方法还是我错过了什么?在典型的 ASP.Net 网站应用程序中,使用更多线程的成本是否会很明显?这是一些示例代码


public async Task<ActionResult> Foo() {

    var dataATask = _dataARepository.GetDataAsync();

    var dataBTask = Task.Run(_dataBRepository.GetData());

    await Task.WhenAll(dataATask, dataBTask);

    var viewModel = new ViewModel(dataATask.Result, dataBTask.Result);

    return View(viewModel);

}


慕运维8079593
浏览 159回答 4
4回答

HUWWW

一般来说,您的代码是可以的 - 它会比原始代码消耗更多的线程和更多的 CPU,但除非您的网站负载很重,否则它不太可能显着影响整体性能。显然,您需要针对您的特定负载(包括 5-10 倍常规流量的某种压力水平负载)自行测量它。包装同步方法Task.Run不是最佳实践(请参阅我应该为同步方法公开异步包装器吗?)。只要您的情况可以接受为这种行为交易额外的线程,它就可能对您有用。如果您只剩下一个同步操作,您可以改为保持同步并在同步步骤结束时等待其余操作,以节省额外的线程:var dataATask = _dataARepository.GetDataAsync();var dataBTaskResult = _dataBRepository.GetData();await Task.WhenAll(dataATask); // or just await dataATask if you have only one.var viewModel = new ViewModel(dataATask.Result, dataBTaskResult);

三国纷争

考虑这两种方法。原来的:public async Task<ActionResult> Foo()&nbsp;{&nbsp; &nbsp; var dataATask = _dataARepository.GetDataAsync();&nbsp; &nbsp; var dataBTask = Task.Run(_dataBRepository.GetData());&nbsp; &nbsp; await Task.WhenAll(dataATask, dataBTask);&nbsp; &nbsp; var viewModel = new ViewModel(dataATask.Result, dataBTask.Result);&nbsp; &nbsp; return View(viewModel);}^此版本将创建一个新线程来调用_dataBRepository.GetData()。附加线程将阻塞,直到调用完成。在等待附加线程完成时,主线程会将控制权交还给 ASP.NET 管道,在那里它可以处理其他用户的请求。不同的:public async Task<ActionResult> Foo()&nbsp;{&nbsp; &nbsp; var dataATask = _dataARepository.GetDataAsync();&nbsp; &nbsp; var dataBResult = _dataBRepository.GetData();&nbsp; &nbsp; await dataATask;&nbsp; &nbsp; var viewModel = new ViewModel(dataATask.Result, dataBResult);&nbsp; &nbsp; return View(viewModel);}^此版本没有为dataBRepository.GetData(). 但它确实阻塞了主线程。所以你的选择是:启动另一个线程,这样您就可以将主线程让给其他任务。坚持主线。如果其他一些任务需要一个线程,它就必须自己启动。在这两种情况下,您一次都使用一个线程(大部分情况下)。在这两种情况下,事务都将在两个后端事务中较慢的一个所需的时间内完成。但是原始选项会启动一个新线程并产生当前线程。这似乎是不需要的额外工作。另一方面,如果该操作需要两个或更多同步后端事务,则原始选项会更快地完成,因为它们可以并行运行。假设他们支持它。如果后端事务是数据库事务并且使用相同的数据库,它们很可能会相互阻塞,在这种情况下您仍然看不到任何好处。

翻过高山走不出你

除了Alexei Levenkov 提到的为异步方法公开同步包装器之外,在 ASP.NET 应用程序上使用Task.Run弊大于利。每个Task.Run都会导致 2 个线程池调度和上下文切换,而没有任何好处。

Smart猫小萌

通常启动一个不同的线程来做一些同步的事情是没有用的,如果你所做的只是等到这个线程完成。相比:async Task<int> ProcessAsync(){&nbsp; &nbsp; var task = Task.Run(() => DoSomeHeavyCalculations());&nbsp; &nbsp; int calculationResult = await task;&nbsp; &nbsp; return calculationResult;}和int ProcessAsync(){&nbsp; &nbsp; return DoSomeHeavyCalculations();}除了 async 函数使用更多的脑力之外,它会限制函数的重用:只有 async 调用者可以使用它。让您的呼叫者决定是否要异步呼叫您。如果他也无事可做,只能等待,他可以让他的呼叫者决定,等等。顺便说一句,这正是它的GetData作用:它不会强迫像你这样的调用者异步,它让你可以自由地调用它同步或使用Task.Run调用它异步。但是,如果您的函数在繁重的计算过程中还有其他事情要做,那么它可能会很有用:async Task<int> ProcessAsync(){&nbsp; &nbsp; var task = Task.Run(() => DoSomeHeavyCalculations());&nbsp; &nbsp; // while the calculations are being performed, do something else:&nbsp; &nbsp; DoSomethingElse();&nbsp; &nbsp; return await task;}在您的示例中:如果只有一个“繁重的计算”,请自己执行此操作。如果有几个繁重的计算,您可以考虑使用Task.Run. 但是不要只是命令其他线程做某事而不自己做任何事情。
打开App,查看更多内容
随时随地看视频慕课网APP