我有一个包含基本信息(如ID)的书籍列表,我需要调用多个外部API端点来获取其余信息,例如图片,参考文献,作者简历等。
这是一种性能不佳的方法,因为检索信息就像同步方式(当时一种方式)......
foreach (var book in books)
{
book.Images = await GetImagesAsync(book.ID);
book.Refs = await GetLinksReferencesAsync(book.ID);
book.AuthorBio = await GetAuthorBioAsync(book.ID);
}
最终我想要的是获得async
来电的优势并同时拨打100个电话。
一项改进是在Task.WaitAll()
内添加foreach
,但唯一的好处是当时要做3次请求。但是,如何以更有效的方式使用async
调用来改善这一点,例如当时的100个请求?
答案 0 :(得分:0)
我真的很想用TPL's dataflow library来做这件事。它允许您将多个异步操作(以及同步操作)链接在一起,形成一个“管道”。并且在每个阶段都有大量的调整来控制并行度,内存缓冲区大小等。
有各种类型的&#39;块&#39;你可以用它来组成一个管道。也许最简单的是TransformBlock<T1,T2>
,它将函数映射为T1
到T2
。还有ActionBlock<T>
执行操作(但不返回值,因此实际上是管道的终止点)还有更多,例如:TransformManyBlock<T1,T2>
,BatchingBlock<T>
,连接块,等更复杂的管道。
对于您自己的示例,您可以按如下方式设置管道。
首先我们定义一些默认选项(例如,我们指定最大缓冲区大小为100本书和15个同时作业的最大并行度):
var defaultOptions = new ExecutionDataflowBlockOptions
{
BoundedCapacity = 100,
MaxDegreeOfParallelism = 15
};
现在为前2个TransformBlock<Book,Book>
作业定义GetXAsync
,为每个作业定义一个ActionBlock,每次使用默认选项终止我们的管道:
var getImagesBlock = new TransformBlock<Book, Book>(async b =>
{
b.Images = await GetImagesAsync(b.ID);
return b;
}, defaultOptions);
var getLinksBlock = new TransformBlock<Book, Book>(async b =>
{
b.Refs = await GetLinksReferencesAsync(b.ID);
return b;
}, defaultOptions);
var getAuthorBioBlock = new ActionBlock<Book>(async b =>
{
b.AuthorBio = await GetAuthorBioAsync(b.ID);
}, defaultOptions);
现在我们定义一些设置来管理我们如何链接块(探索这些最适合您自己的解决方案!):
var linkOptions = new DataflowLinkOptions
{
PropagateCompletion = true //when an earlier block signals it is 'Complete' and has no more messages the next block completes too after it has finished any existing messages
};
然后我们将所有3个块连接起来构建管道:
getImagesBlock.LinkTo(getImagesBlock, linkOptions);
getLinksBlock.LinkTo(getAuthorBioBlock, linkOptions);
现在我们需要做的就是将每本书传递到管道的开头:
foreach (var book in books)
{
getImagesBlock.Post(book); // or we could use SendAsync if this is inside an async method
}
向我们已完成发送图书的第一个块发出信号:
getImagesBlock.Complete();
然后等待最后一个块完成处理:
getAuthorBioBlock.Completion.Wait(); // or await getAuthorBioBlock.Completion; if inside an async method
我认为使用数据流选项,一旦习惯了它们,就可以为这样的大量并行操作提供一种自然,易用且广泛适用的解决方案。我建议花点时间学习如何使用它。它确实使这种类型的工作更容易管理和优化。
顺便提一下注意事项:
book
的每个操作都必须按顺序执行,如OP中的情况。答案 1 :(得分:0)
试试这个:
foreach (var book in books)
{
var imagesTask = GetImagesAsync(book.ID);
var refsTask = GetLinksReferencesAsync(book.ID);
var authorTask = GetAuthorBioAsync(book.ID);
Task.WaitAll(imagesTask, refsTask, authorTask);
book.Images = imagesTask.Result;
book.Refs = refsTask.Result;
book.AuthorBio = authorTask.Result;
}
在这种方法中,三个异步任务同时执行。