为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
慕运维8079593 2022-06-18 16:28:39
我的一位同事重构了我们的控制器方法,以便我们所有的 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);}
查看完整描述

4 回答

?
HUWWW

TA贡献1874条经验 获得超12个赞

一般来说,您的代码是可以的 - 它会比原始代码消耗更多的线程和更多的 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);


查看完整回答
反对 回复 2022-06-18
?
三国纷争

TA贡献1804条经验 获得超7个赞

考虑这两种方法。


原来的:


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);

}

^此版本将创建一个新线程来调用_dataBRepository.GetData()。附加线程将阻塞,直到调用完成。在等待附加线程完成时,主线程会将控制权交还给 ASP.NET 管道,在那里它可以处理其他用户的请求。


不同的:


public async Task<ActionResult> Foo() 

{

    var dataATask = _dataARepository.GetDataAsync();

    var dataBResult = _dataBRepository.GetData();

    await dataATask;

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

    return View(viewModel);

}

^此版本没有为dataBRepository.GetData(). 但它确实阻塞了主线程。

所以你的选择是:

  1. 启动另一个线程,这样您就可以将主线程让给其他任务。

  2. 坚持主线。如果其他一些任务需要一个线程,它就必须自己启动。

在这两种情况下,您一次都使用一个线程(大部分情况下)。在这两种情况下,事务都将在两个后端事务中较慢的一个所需的时间内完成。但是原始选项会启动一个新线程并产生当前线程。这似乎是不需要的额外工作。

另一方面,如果该操作需要两个或更多同步后端事务,则原始选项会更快地完成,因为它们可以并行运行。假设他们支持它。如果后端事务是数据库事务并且使用相同的数据库,它们很可能会相互阻塞,在这种情况下您仍然看不到任何好处。


查看完整回答
反对 回复 2022-06-18
?
翻过高山走不出你

TA贡献1875条经验 获得超3个赞

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



查看完整回答
反对 回复 2022-06-18
?
Smart猫小萌

TA贡献1911条经验 获得超7个赞

通常启动一个不同的线程来做一些同步的事情是没有用的,如果你所做的只是等到这个线程完成。


相比:


async Task<int> ProcessAsync()

{

    var task = Task.Run(() => DoSomeHeavyCalculations());

    int calculationResult = await task;

    return calculationResult;

}


int ProcessAsync()

{

    return DoSomeHeavyCalculations();

}

除了 async 函数使用更多的脑力之外,它会限制函数的重用:只有 async 调用者可以使用它。让您的呼叫者决定是否要异步呼叫您。如果他也无事可做,只能等待,他可以让他的呼叫者决定,等等。


顺便说一句,这正是它的GetData作用:它不会强迫像你这样的调用者异步,它让你可以自由地调用它同步或使用Task.Run调用它异步。


但是,如果您的函数在繁重的计算过程中还有其他事情要做,那么它可能会很有用:


async Task<int> ProcessAsync()

{

    var task = Task.Run(() => DoSomeHeavyCalculations());


    // while the calculations are being performed, do something else:

    DoSomethingElse();


    return await task;

}

在您的示例中:如果只有一个“繁重的计算”,请自己执行此操作。如果有几个繁重的计算,您可以考虑使用Task.Run. 但是不要只是命令其他线程做某事而不自己做任何事情。


查看完整回答
反对 回复 2022-06-18
  • 4 回答
  • 0 关注
  • 158 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信