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()也充满了危险?

ITargetBlock<TInput>的最佳实践.Completion.ContinueWith()

虽然您可以自己实现完成传播,但 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中的任何异常并删除警告。