C# 使用2个等待的I/O绑定任务运行,还是使用经典的Fork/Join方法?
假设我们有2个I/O绑定的任务需要处理,其中有N个元素。我们可以将这两个任务称为A和BB只能在A产生结果后运行 我们可以通过两种方式实现这一点。(请忽略访问修改关闭的情况。) 任务。运行方式:C# 使用2个等待的I/O绑定任务运行,还是使用经典的Fork/Join方法?,c#,.net,asynchronous,.net-core,async-await,C#,.net,Asynchronous,.net Core,Async Await,假设我们有2个I/O绑定的任务需要处理,其中有N个元素。我们可以将这两个任务称为A和BB只能在A产生结果后运行 我们可以通过两种方式实现这一点。(请忽略访问修改关闭的情况。) 任务。运行方式: List<Task> workers = new List<Task>(); for (int i = 0; i < N; i++) { workers.Add(Task.Run(async () => { await A(i);
List<Task> workers = new List<Task>();
for (int i = 0; i < N; i++)
{
workers.Add(Task.Run(async () =>
{
await A(i);
await B(i);
}
}
await Task.WhenAll(workers);
List workers=new List();
对于(int i=0;i
{
等待A(i);
等待B(i);
}
}
等待任务。何时(工人);
经典分叉/连接:
List<Task> workersA = new List<Task>();
List<Task> workersB = new List<Task>();
for (int i = 0; i < N; i++)
{
workersA.Add(A(i));
}
await Task.WhenAll(workersA);
for (int i = 0; i < N; i++)
{
workersB.Add(B(i));
}
await Task.WhenAll(workersB);
List workersA=new List();
List WORKERB=新列表();
对于(int i=0;i
或者,也可以通过以下方式进行:
List<Task> workers = new List<Task>();
for (int i = 0; i < N; i++)
{
workers.Add(A(i));
}
for (int i = 0; i < N; i++)
{
await workers[i];
workers[i] = B(i);
}
await Task.WhenAll(workers);
List workers=new List();
对于(int i=0;i
我担心的是MSDN文档声明我们永远不应该使用Task.Run进行I/O操作
考虑到这一点,处理这一案件的最佳方法是什么
如果我错了,请纠正我的错误,但我们希望避免使用Task.Run,因为我们可以有效地将线程排队来处理工作,如果我们只使用wait,那么就在那里(因为操作是I/O)
我真的很想沿着Task.Run的路线走下去,但是如果它最终没有明显的原因而使用了线程/增加了额外的开销,那么就不可能了
我真的很想完成这项任务。走这条路
为什么?
但是,如果它最终使用线程没有明显的原因,那么它是不可能的
它说:
对要在线程池上运行的指定工作进行排队
这并不一定意味着每次调用任务时都会有一个全新的线程。运行。可能会,但不一定。您可以保证的是,它将在一个不是当前线程的线程上运行
您无法控制创建多少线程来完成所有这些工作。但是建议不要使用Task.Run
进行I/O操作是合理的。这是没有收益的不必要的开销。这样会降低效率
您的其他任何一个解决方案都可以正常工作。您的上一个解决方案可能会更快完成,因为您将更快地开始调用B()
(您只需等待第一个A()
完成,然后再开始调用B()
)
根据Theodor的答案更新:我们都是对的:)重要的是要知道异步方法中的所有代码在第一次等待之前(以及之后的代码,除非您另有规定)将在启动它的相同上下文中运行。在桌面应用中,这是UI线程。等待是异步的。因此,UI线程在等待时被释放。但是,如果该方法中有任何CPU繁重的工作,它将锁定UI线程
所以Theodor说你可以使用任务。尽快运行以使其脱离UI线程,并保证它永远不会锁定UI线程。虽然这是真的,但你不能到处盲目地使用该建议。首先,在I/O操作后,你可能需要在UI中做一些事情,而这必须在UI线程上完成。如果你使用 运行
,然后您必须确保返回到UI线程以完成该工作
但是,如果您调用的异步方法有足够的CPU绑定工作来冻结UI,那么它就不是严格意义上的I/O操作,并且“使用Task.Run
进行CPU绑定工作,async
/等待进行I/O”的建议仍然适用
我能说的就是:试试看。如果你发现无论你做什么都会冻结UI,那么就使用Task.Run
。如果你发现它没有冻结UI,那么Task.Run
是不必要的开销(请注意,这并不多,但仍然是不必要的,但如果你像现在这样在循环中做,情况会变得更糟)
所有这些都适用于桌面应用程序。如果你在ASP.NET中,那么Task.Run
不会为你做任何事情,除非你尝试并行执行某些操作。在ASP.NET中,没有“UI线程”,因此不管你在哪个线程上执行工作。你只想确保在等待时不会锁定线程(因为ASP.NET中的线程数量有限)
如果您的工作是I/O绑定的,请使用异步
和等待
,而不使用任务。运行
。您不应使用任务并行库。原因如中所述
不过,这条建议有误导性。通过阻止I/O操作的Task.Run
,作者可能想到了以下几点:
var data = await Task.Run(() =>
{
return webClient.DownloadString(url); // Blocking call
});
…这确实很糟糕,因为它会阻止线程池线程。但是使用异步委托运行Task.Run
是完全可以的:
var data = await Task.Run(async () =>
{
return await webClient.DownloadStringTaskAsync(url); // Async call
});
实际上,这是从UI应用程序的事件处理程序启动异步操作的首选方法,因为它可以确保立即释放UI线程。如果您按照本文的建议,省略了任务。请运行:
private async void Button1_Click(object sender, EventArgs args)
{
var data = await webClient.DownloadStringTaskAsync(url);
}
…然后,您可能会面临异步方法可能不是100%异步的风险,并且可能会阻塞UI线程。对于专家编写的内置异步方法,这是一个很小的问题,但对于第三方异步方法来说,这是一个更大的问题,对于开发人员自己编写的异步方法来说,这是一个更大的问题
因此,关于您问题的选项,我认为第一个(Task.Run方式)是最安全和最有效的。第二个将分别等待所有A和所有B任务,因此持续时间最多为Max(A)+Max(B)。从统计上看,它应该比Max(A+B)长。还有一种方式:异步任务