Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice for ITargetBlock<TInput>.Completion.ContinueWith()

This question is about best practices when using ContinueWith() to handle a TPL datablock's completion.

The ITargetBlock<TInput>.Completion() method allows you to asynchronously handle a datablock's completion using ContinueWith().

Consider the following Console app code which demonstrates a very basic use:

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;
}

The code has a very simple TransformBlock which divides integers by 2.0 and turns them into doubles. The transformed data is processed by an ActionBlock which just outputs the values to the console window.

The output is:

0
0.5
1
1.5
2
2.5
3
3.5
4
4.5

When the TransformBlock is completed, I want to also complete the ActionBlock. It is convenient to do this like so:

transform.Completion.ContinueWith(continuation => output.Complete());

And here lies the issue. Because this is inside an async method and I'm ignoring the return value from ContinueWith(), I get a compiler warning:

warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Clearly I can't await the call as advised by the warning - if I do so, the code hangs up because the CompleteWith() task can't complete until transform.Complete() is called.

Now I don't have a problem with dealing with the warning itself (I can just suppress it or assign the task to a variable, or use a .Forget() task extension and so on) - but here's my questions:

  • Is it safe to handle linked DataBlock completion in this way?
  • Is there a better way?
  • Is there anything important here that I've overlooked?

I think that if I was doing anything other than output.Complete() inside the continuation, I'd have to provide exception handling - but perhaps even just calling output.Complete() is fraught with peril?

like image 618
Matthew Watson Avatar asked Dec 09 '14 14:12

Matthew Watson


1 Answers

While you can implement completion propagation yourself, TPL Dataflow does it for you with DataflowLinkOptions.PropagateCompletion:

transform.LinkTo(output, new DataflowLinkOptions {PropagateCompletion = true});

If you would still like to implement that yourself, you can solve the issue be storing the task and awaiting it when appropriate using 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)
}

This enables you to handle any exceptions in output.Complete and to remove the warning.

like image 171
i3arnon Avatar answered Oct 15 '22 05:10

i3arnon