3

I am using the TransformBlock from the TPL Dataflow library, and I have realized that when a exception is thrown during the transform, I get a generic exception in the "Receive" method, but no mention to the original one.

In this code:

Func<Int32, Task<String>> transformer = async i => { await Task.Yield(); throw new ArgumentException("whatever error"); };
TransformBlock<Int32, String> transform = new TransformBlock<int, string>(transformer);
transform.Post(1);

try
{
    var x = await transform.ReceiveAsync();
}
catch (Exception ex)
{
    // catch
}

The exception ex contains:

System.InvalidOperationException was caught
  HResult=-2146233079
  Message=The source completed without providing data to receive.
  Source=System.Threading.Tasks.Dataflow
  StackTrace:
       at System.Threading.Tasks.Dataflow.Internal.Common.InitializeStackTrace(Exception exception)
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
       at DoubleQueueTest.Program.<testwhatever>d__5.MoveNext() in c:\Users\vtortola\Documents\Visual Studio 2013\Projects\DoubleQueueTest\DoubleQueueTest\Program.cs:line 43
  InnerException: 

No mention to the original exception type or its message. Is there a way to force it to throw the original one? or at least, use it as inner exception?

svick
  • 236,525
  • 50
  • 385
  • 514
vtortola
  • 34,709
  • 29
  • 161
  • 263

2 Answers2

5

You're seeing an exception from ReceiveAsync. The InvalidOperationException is expected behavior.

If you want to detect or respond to block faults, then await the IDataflowBlock.Completion property.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • But what if the transform never ends? or if just some of the items fail? – vtortola Mar 19 '14 at 23:07
  • If the transformation of any item fails, the block faults and will not accept any additional items. If the transformation never ends, then that's a bug - just like a neverending `while` loop. – Stephen Cleary Mar 20 '14 at 01:45
  • I wanted to mean that it is a continuous transformation because new items arrive and the transformation should not stop because one of them fails. But I get the point. – vtortola Mar 20 '14 at 12:32
  • OK. If you don't want the block to fault, then you should catch any exceptions within your transformation delegate. – Stephen Cleary Mar 20 '14 at 12:34
0

I am adding this response just for completion. As @Stephen Clearly stated, the transform will fault if one of the items faults. So if your transform should not stop because a single item faults, I have used this approach:

Create a small class that represents the operation result:

 class TransformResult<T>
 {
     public T Result { get; set; }
     public ExceptionDispatchInfo Error { get; set; }
 }

If there is an exception, capture it and return the result:

Func<Int32, Task<TransformResult<String>>> transformer = async i => 
{ 
    await Task.Yield();
    try
    {
        // do whatever

        throw new ArgumentException("whatever error");
    }
    catch (Exception ex)
    {
        return new TransformResult<String>() { Error = ExceptionDispatchInfo.Capture(ex) };
    }
};

And when awaiting the transform result, if the result contains an error... throw it:

var transform = new TransformBlock<int, TransformResult<String>>(transformer);
transform.Post(1);

try
{
    var x = await transform.ReceiveAsync();
    if (x.Error != null)
        x.Error.Throw();
}
catch (Exception ex)
{
    // catch
}
vtortola
  • 34,709
  • 29
  • 161
  • 263