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:
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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With