等待和并行

时间:2017-03-17 20:12:48

标签: c# async-await

考虑以下电话:

Getstuff(await getFoo, await getBar,  await getQuux);

内部Getstuff

var fooLookup = getFoo?.Entries.ToLookup(x => x.Something);
var barDictionary = getBar?.Entries.ToDictionary(x => x.Something);
var quuxDictionary = getQuux.Entries.ToDictionary(x => x.Something);

我的印象是错过了可以通过以下方式给出的并发机会:

Getstuff(getFoo, getBar, getQuux);

Getstuff

Task<getFooLookup> fooLookup1;
Task<getBarDictionary> barDictionary1;
Task<getQuuxDictionary> quuxDictionary1;

Task.WaitAll(fooLookup1 = getFoo, barDictionary1 = getBar, quuxDictionary1 = getQuux);

var fooLookup = fooLookup1.Result.Entries.ToLookup(x => x.Something); 
var barDictionary = barLookup1.Result?.Entries.ToDictionary(x => x.Something); 
var quuxDictionary = quuxLookup1.Result?.Entries.ToDictionary(x => x.Something);

第二个选项是否可以并行化(如果可行),如果是,那么我将承担哪些风险,例如涉及数据库查找?我可能会遗漏任何简化?

修改

这三个参数分配如下:

async Task<foo> GetFoo(ICollection<Guid> stuffList) { .. some async code.. }
...
var getFoo = GetFoo(list);

2 个答案:

答案 0 :(得分:1)

如果你想实现并行性并且你有3个返回说Task<T1>Task<T2>Task<T3>的方法,那很简单:首先启动所有任务,但是不等待任何人,只有等待,或者等待所有这些与await Task.WhenAll()一起(理想情况下;否则不太适合使用阻止Task.WaitAll()),或者单独使用await 。所以像这样:

Task<T1> getFooTask = GetFoo();
Task<T2> getBarTask = GetBar();
Task<T3> getQuuxTask = GetQuux();
// All tasks started (or at least queued) now, potentially (probably) running in parallel

await Task.WhenAll(getFooTask, getBarTask, getQuuxTask);
// All tasks complete now

然后您可以单独检查结果:

T1 fooResults = await getFooTask;
T2 barResults = await getBarTask;
T3 quuxResults = await getQuuxTask;

或者也许:

Getstuff(await getFooTask, await getBarTask,  await getQuuxTask);

此时您也可以使用.Result而不会阻止,但再次使用await再次重构。

要单独等待它们(并且仍然实现并行性),只需删除await Task.WhenAll()行。

作为旁注,放弃await Task.WhenAll()是一个简单示例,显示了对已完成任务而不是await使用.Result的好处。如果您使用了.Result,则行为会从非阻止更改为阻止。

答案 1 :(得分:0)

你的情景很模糊。我在这里假设所有这三个等待的任务已经同时运行,这样你的原始代码就会同步等待各个结果。

首先,重要的是要指出您对代码的建议更改会对代码的基本行为产生重大不利影响。如果代码用于可能允许异步等待完成,则建议的更改会强制线程同步等待完成。这应该避免。

我认为这是相当明显的,但从评论来看,有些人对我的意图感到困惑,因此这个明确的警告。考虑到这一点......

您的第二个示例,忽略您的语法错误,可以(并且应该)简化为:

Task.WaitAll(getFoo, getBar, getQuux);

var fooLookup = getFoo.Result.Entries.ToLookup(x => x.Something); 
var barDictionary = getBar.Result?.Entries.ToDictionary(x => x.Something); 
var quuxDictionary = getQuux.Result?.Entries.ToDictionary(x => x.Something);

但实际上,如果你的方法本身并不是一个异步操作,你就不应该强迫它。调用者可以根据需要做到这一点很简单:

Task.WaitAll(getFoo, getBar, getQuux);
GetStuff(await getFoo, await getBar, await getQuux);

WaitAll()强制执行您的示例所具有的同步行为。同样,这并不是理想的。只是这是必需的,以便与您的示例保持一致。

通过这种方式,在您尝试将结果传递给GetStuff()并且GetStuff()可以保持其良好,简单的同步自我之前,确保完成所有三项任务。

使用await只是帮助解压缩异常,使其更易于处理,记录等。当然,您可以使用例如getFoo.Result代替await getFoo

但你甚至不需要那个!假设这三个任务都已经运行,那么依次等待所有三个任务并不会以任何方式阻止它们同时运行。即使第一个是最后一个完成,当一个完成时,另外两个也将有,等待的那些将立即完成。

而且重要的是,以这种方式执行此操作可以防止在等待结果可用时阻塞线程。这比你提议的同步等待要好得多。

换句话说,您的原始代码很好,并且比您提议的替代方案更好。不要改变它。