ITargetBlock的最佳实践.Completion.ContinueWith()
本文关键字:Completion ContinueWith 最佳 TInput ITargetBlock | 更新日期: 2023-09-27 18:33:45
这个问题是关于使用ContinueWith()
处理TPL数据块完成时的最佳实践。
ITargetBlock<TInput>.Completion()
方法允许您使用 ContinueWith()
异步处理数据块的完成。
请考虑以下控制台应用代码,该代码演示了一个非常基本的用法:
private static void Main()
{
test().Wait();
}
static async Task test()
{
var transform = new TransformBlock<int, double>(i => i/2.0);
var output = new ActionBlock<double>(d => Console.WriteLine(d));
// Warning CS4014 here:
transform.Completion.ContinueWith(continuation => output.Complete());
transform.LinkTo(output);
for (int i = 0; i < 10; ++i)
await transform.SendAsync(i);
transform.Complete();
await output.Completion;
}
该代码有一个非常简单的TransformBlock
将整数除以 2.0 并将它们转换为双精度。转换后的数据由一个ActionBlock
处理,该仅将值输出到控制台窗口。
输出为:
0
0.5
1
1.5
2
2.5
3
3.5
4
4.5
当TransformBlock
完成时,我也想完成ActionBlock
。这样做很方便:
transform.Completion.ContinueWith(continuation => output.Complete());
问题就在这里。因为这是在 async
方法中,并且我忽略了 ContinueWith()
的返回值,所以我收到编译器警告:
警告 CS4014:由于未等待此调用,因此在调用完成之前将继续执行当前方法。请考虑将"await"运算符应用于调用结果。
显然,我无法按照警告的建议await
调用 - 如果我这样做,代码就会挂起,因为CompleteWith()
任务在调用transform.Complete()
之前无法完成。
现在我在处理警告本身方面没有问题(我可以抑制它或将任务分配给变量,或使用.Forget()
任务扩展等( - 但这是我的问题:
- 以这种方式处理链接的数据块完成是否安全?
- 有没有更好的方法?
- 这里有什么重要的事情是我忽略的吗?
我认为,如果我在延续中做output.Complete()
以外的任何事情,我将不得不提供异常处理 - 但也许即使只是调用output.Complete()
也充满了危险?
虽然您可以自己实现完成传播,但 TPL 数据流会为您完成DataflowLinkOptions.PropagateCompletion
:
transform.LinkTo(output, new DataflowLinkOptions {PropagateCompletion = true});
如果您仍然想自己实现它,您可以使用Task.WhenAll
来解决存储任务并在适当时等待它的问题:
static async Task test()
{
var transform = new TransformBlock<int, double>(i => i/2.0);
var output = new ActionBlock<double>(d => Console.WriteLine(d));
// Warning CS4014 here:
var continuation = transform.Completion.ContinueWith(_ => output.Complete());
transform.LinkTo(output);
for (int i = 0; i < 10; ++i)
await transform.SendAsync(i);
transform.Complete();
await Task.WhenAll(continuation, output.Completion)
}
这使您能够处理output.Complete
中的任何异常并删除警告。