I am using TaskParallelLibrary DataFlow
combined with Try
library designed by Stephen Cleary (https://github.com/StephenCleary/Try) to achieve what is called "railroad programming" so I could pass Exception
data down the pipe. I would like to know to if it is somehow possible in ActionBlock
to get some context of what or (in my case) exactly which item caused the Exception
?
Here is small sample code:
public async Task TestRailroadException(List<int> constructIds)
{
var downloadBlock = new TransformBlock<int, Try<int>>(
construct => Try.Create(() =>
{
//ThisMethodMyThrowException();
return 1;
}));
var processBlock = new TransformBlock<Try<int>, Try<int>>(
construct => construct.Map(value =>
{
//ThisMethodMyAlsoThrowException();
return 1;
}));
var resultsBlock = new ActionBlock<Try<int>>(construct =>
{
if (construct.IsException)
{
var type = construct.Exception.GetType();
//Here it would be nice to know which item(id) was faulted.
}
});
downloadBlock.LinkTo(processBlock, new DataflowLinkOptions
{ PropagateCompletion = true });
processBlock.LinkTo(resultsBlock, new DataflowLinkOptions
{ PropagateCompletion = true });
foreach (var constructId in constructIds)
{
await downloadBlock.SendAsync(constructId);
}
downloadBlock.Complete();
await resultsBlock.Completion;
}
You could use ValueTuple<TId, Try<TResult>>
structs as messages for the pipeline, but it may be slightly more convenient to create a custom wrapper of the Try
class that also holds the id. Since this wrapper will have two type parameters, it is allowed to name it Try
as well:
public readonly struct Try<TId, TResult>
{
public static Try<TId, TResult> Create(TId id, Func<TResult> func)
=> new Try<TId, TResult>(id, Try.Create(func));
public static async Task<Try<TId, TResult>> Create(TId id,
Func<Task<TResult>> func)
=> new Try<TId, TResult>(id, await Try.Create(func).ConfigureAwait(false));
public readonly TId Id { get; }
public readonly Try<TResult> Result { get; }
private Try(TId id, Try<TResult> result) { Id = id; Result = result; }
public Try<TId, TNewResult> Map<TNewResult>(Func<TResult, TNewResult> func)
=> new Try<TId, TNewResult>(Id, Result.Map(func));
public async Task<Try<TId, TNewResult>> Map<TNewResult>(
Func<TResult, Task<TNewResult>> func)
=> new Try<TId, TNewResult>(Id, await Result.Map(func).ConfigureAwait(false));
}
You could then use it like this:
var downloadBlock = new TransformBlock<int, Try<int, int>>(
construct => Try<int, int>.Create(construct, async () =>
{
await SometimesThrowsAsync();
return 1;
}));
var processBlock = new TransformBlock<Try<int, int>, Try<int, int>>(
construct => construct.Map(async value =>
{
await SometimesThrowsAsync();
return 1;
}));
var resultsBlock = new ActionBlock<Try<int, int>>(construct =>
{
if (construct.Result.IsException)
{
var type = construct.Result.Exception.GetType();
//Log that the {construct.Id} has failed.
}
});