I am using TPL
blocks to carry out operation that may be cancelled by user:
I have come up with two options, in first I cancel whole block but don't cancel operation inside the block, like this:
_downloadCts = new CancellationTokenSource();
var processBlockV1 = new TransformBlock<int, List<int>>(construct =>
{
List<int> properties = GetPropertiesMethod(construct );
var entities = properties
.AsParallel()
.Select(DoSometheningWithData)
.ToList();
return entities;
}, new ExecutionDataflowBlockOptions() { CancellationToken = _downloadCts.Token });
and the second I cancel inside operation, but not the block itself:
var processBlockV2 = new TransformBlock<int, List<int>>(construct =>
{
List<int> properties = GetPropertiesMethod(construct);
var entities = properties
.AsParallel().WithCancellation(_downloadCts.Token)
.Select(DoSometheningWithData)
.ToList();
return entities;
});
As I understand, first option will cancel whole block, thus shutting down the whole pipeline. My question is will it also cancel inside operation and dispose of all resources if there are any(open StreamReaders, ect.) or is it better to choose second option for then I myself can make sure everything is cancelled and cleaned and then I could use some means(railway programming) to float raised OperationCanceledException
down the pipe and deal with it where I want?
These two options are not equivalent.
The first option (CancellationToken = _downloadCts.Token
) will make the processBlockV1
block to discard any messages that are currently in its buffer (its InputCount
property will become 0
), and stop accepting new messages (invoking its Post
method will invariably return false
). It will not stop processing the messages that are currently in progress though. These will be processed fully, but will not be propagated to any linked blocks. After these messages are processed, the block will complete in a canceled state (its Completion
.Status
property will become Canceled
).
The second option (canceling the inside operations) will have no effect to the block as a whole. The Dataflow blocks tolerate any OperationCanceledException
thrown from their processing function, and just ignore the faulted item and proceed with the next one. So after the cancellation of the token, all posted messages are still going to be processed, and the block will continue accepting more. It just won't propagate anything to its linked blocks, because all items will throw the OperationCanceledException
and get ignored. In the specific example the GetPropertiesMethod
method will be invoked for all construct
messages, and so will impose a delay to the completion of the block. The final state of the block will be RanToCompletion
.
It is important to know that the Dataflow blocks are taking seriously the concept of Completion
. They will wait for everything that they know to stop running before reporting completion. In case you do want them to complete prematurely and leave behind tasks that are still running, you'll have to do tricks like wrapping your tasks with cancelable wrappers.