OperationCanceledException thrown in tpl dataflow block gets swallowed - c#

For some reason when an OperationCanceledException gets thrown inside an IDataflowBlock, the block does not propagate this exception to its IDataflowBlock.Completion task. Running the code sample below returns an unexpected IDataflowBlock.Completion.Status == TaskStatus.RanToCompletion.
However, if the thrown exception type in the block is changed to an ArgumentNullException, the IDataflowBlock.Completion.Status changes to TaskStatus.Faulted and the exception is saved in its InnerException property.
Any ideas why OperationCanceledException is getting swallowed?
[TestFixture]
public class TplDataBlockExceptionTest
{
[Test]
public void ShouldThrowException()
{
// Arrange
var block = new TransformBlock<int, string>(i =>
{
throw new OperationCanceledException();
return i.ToString();
});
// Act
block.Post(1);
block.Complete();
try
{
block.Completion.Wait();
}
catch (Exception)
{
// ignored
}
// Assert
Assert.That(block.Completion.IsFaulted);
}
}

I was able to reach Stephen Toub at Microsoft and he was able to confirm that this behavior is by design:
https://github.com/dotnet/corefx/blob/master/src/System.Threading.Tasks.Dataflow/src/Blocks/TransformBlock.cs#L186-L190
https://github.com/dotnet/corefx/blob/master/src/System.Threading.Tasks.Dataflow/src/Internal/Common.cs#L152-L175

Related

Would it make sense to always account for multiple exceptions in AggregateExceptions when calling into async code?

When calling into async code like for example productUpdate.UpdateAsync(...), there are chances that it could throw an AggregateException having multiple inner exceptions or just one exception. This all depends on how UpdateAsync was implemented internally.
Question:
Since await unwraps only the first exception in the list of exceptions within an AggregateException, the following special code tries to circumvent that, but this is clunky and ideally in every place where I am calling into some external library's async code, there could be an AggregateException with multiple exceptions. Would it make sense to have this in all those places? (sure probably could move into a helper method but that's not the point here) and also then there's the question of what meaningful things I am doing by catching these exceptions.
I think it does NOT make sense in all places. Your thoughts?
var t1 = FooAsync();
var t2 = BarAsync();
var allTasks = Task.WhenAll(t1, t2);
try
{
await allTasks;
}
catch (Exception)
{
var aggEx = allTasks.Exception as AggregateException;
// todo: do something with aggEx.InnerExceptions
}
Update:
Added whole repro code here for user Dave and the result of running it:
using System;
using System.Threading.Tasks;
class Example
{
static void Main()
{
BlahAsync().Wait();
}
static async Task BlahAsync()
{
var t1 = FooAsync(throwEx: true);
var t2 = BarAsync(throwEx: true);
var allTasks = Task.WhenAll(t1, t2);
try
{
await allTasks;
}
catch (AggregateException agex)
{
Console.WriteLine("Caught an aggregate exception. Inner exception count: " + agex.InnerExceptions.Count);
}
}
static async Task FooAsync(bool throwEx)
{
Console.WriteLine("FooAsync: some pre-await code here");
if (throwEx)
{
throw new InvalidOperationException("Error from FooAsync");
}
await Task.Delay(1000);
Console.WriteLine("FooAsync: some post-await code here");
}
static async Task BarAsync(bool throwEx)
{
Console.WriteLine("BarAsync: some pre-await code here");
if (throwEx)
{
throw new InvalidOperationException("Error from BarAsync");
}
await Task.Delay(1000);
Console.WriteLine("BarAsync: some post-await code here");
}
}
Result:
FooAsync: some pre-await code here
BarAsync: some pre-await code here
Unhandled Exception: System.AggregateException: One or more errors occurred. (Error from FooAsync) ---> System.InvalidOperationException: Error from FooAsync
at Example.<FooAsync>d__2.MoveNext() in C:\Users\foo\source\repos\ConsoleApp9\ConsoleApp9\UnderstandingCallStack.cs:line 37
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Example.<BlahAsync>d__1.MoveNext() in C:\Users\foo\source\repos\ConsoleApp9\ConsoleApp9\UnderstandingCallStack.cs:line 20
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.Wait()
at Example.Main() in C:\Users\foo\source\repos\ConsoleApp9\ConsoleApp9\UnderstandingCallStack.cs:line 8
As mentioned in my comment you can explicitly catch an Aggregate exception like so
try
{
//do you're async code
}
catch (AggregateException ae)
{
//handle it
}
you can add additional catch statements to deal with other exception types, but remember always start with the most specific exception type first and if you have a catch all exception handler (which can be argued you shoudn't but thats not for now) that should always be the last catch statement.
Now when you can catch an AggregateException, as you say, it can contain many exceptions and each of those can contain inner exceptions as well, so it has the potential to be a complete structure of nested exceptions. But don't fear! .NET has provided help. You can called .Flatten() on your aggregate exception. This will return you a new Aggreagete exception that has a flat list of its inner exceptions, not a nested list, so you can easily, iterate over it or just take the top one, whatever you need to do.
try
{
//async code stuff
}
catch(AggregateException ae)
{
var allExceptions = ae.Flatten().InnerExceptions;
// now do what you want with the list of exceptions
}
catch (Exception x)
{
// ooo look here is an example of the second catch statement catching any other exception that isnt an AggregateException)
}
Now another cool thing you can do with aggregate exceptions when you catch them, is pass a custom exception handler to the Handle method on the AE instance. This is useful if you want handle specific kinds of exceptions such as a FileNotFoundException but if there are any other exceptions that should be thrown as another Aggregate Exception for the caller to handle. Like this
try
{
//async stuff
}
catch (AggregateException ae)
{
ae.Handle(x =>
{
if (x is FileNotFoundException)
{
Console.WriteLine("I handled the file not found, don't worry");
return true;
}
return false
});
}
What happens here is that each exception in the Aggregate Exceptions inner exceptions is passed to the handler, we return true, if we handle it and false if we don't. All the exceptions that were not handled (ie we returned false for) are added as the inner exceptions of a new Aggregate exception.
This last part might not be relevant to you, if you just want to the handle the Aggregate exception whatever it contains, but its a good thing to have in the tool box IMO

NSubstitute, try catch is not working on async method configured to throw an exception

I'm using NSubstitute for mocking and faking. I'm working with EF6 and would like to setup the SaveChangesAsync-Method of the database context to throw an exception:
context.SaveChangesAsync().Throws(new DbUpdateException("", SqlExceptionHelper.CreateSqlException(2627)));
SaveChangesAsync is called within a method of my data repository like this:
try
{
var fromDatabase = await context.Entries.OfType<Document>().FirstOrDefaultAsync(d => d.Id == doc.Id);
if (fromDatabase == null)
{
fromDatabase = new Document();
context.Entries.Add(fromDatabase);
}
PatchEntity(fromDatabase, doc);
await context.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
var innerException = ex.InnerException as SqlException;
if (innerException != null && innerException.Number == 2627)
{
errors.Add(new DbValidationError(nameof(doc.Name), "A entry with the same Name already exists under the selected parent."));
}
}
And this is the line within my unit test:
var result = await repository.TryAddOrUpdateDocument(doc);
Unfortunately my test keeps failing with the reason, that my test method(!) is throwing the exception, I'm trying to catch. Adding a general exception catch block isn't working either, the exception is not being catched at all. The exception is bubbling up.
My test is declared as "public async Task...", but turning it into simply void and calling .Result on the async method of my repository doesn't help either. What is going on?
I think the problem is that the exception is thrown from the original call instead of from inside the returned Task as described here.
Try something like:
Func<int> throwDbEx = () => {
throw new DbUpdateException("", SqlExceptionHelper.CreateSqlException(2627));
};
context.SaveChangesAsync().Returns(Task.Run(throwDbEx));
I cannot be 100% sure, but you are probably facing the problem that exceptions thrown by async with void return type cannot be caught naturally. Read the section "Avoid Async Void" here:
https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
Even if it won't answer your problem, it is worth reading anyway...

How to catch ReactiveCommand Exceptions?

I have a basic ReactiveCommand. No async wizardry, just plain old ReactiveCommand.Create(). I Subscribe() with the overload that takes an exception handler, but never hit the breakpoint in said exception handler (I did not expect this). I subscribe to ThrownErrors, never hit the breakpoint in that exception handler either (I sort of expected this).
Here's the example code:
var myCommand = ReactiveCommand.Create();
// this does not seem to work
myCommand.Subscribe(
_ => { throw new Exception("oops"); },
ex => {
Console.WriteLine(ex.Mesage);
Debugger.Break();
});
//this does not seem to work either
myCommand.ThrownExceptions.Subscribe(
ex => {
Console.WriteLine(ex.Mesage);
Debugger.Break();
});
I did my homework and checked the questions and answers in the topic.
ReactiveUI exception handling
How to catch exception from ReactiveCommand?
I have checked the mail list as well, and found this:
https://groups.google.com/forum/#!topic/reactivexaml/Dkc-cSesKPY
So I decided to change this to some async solution:
var myCommand = ReactiveCommand.CreateAsyncObservable(_ => this.Throw());
myCommand.Subscribe(
_ => { Console.WriteLine("How did we get here?"); },
// this is not expected to work
ex => {
Console.WriteLine(ex.Message);
Debugger.Break();
});
// however, I sort of expect this to work
myCommand.ThrownExceptions.Subscribe(
ex => {
Console.WriteLine(ex.Message);
Debugger.Break();
});
[...]
private IObservable<object> Throw()
{
Debugger.Break();
throw new Exception("oops");
}
And yet, I never hit any of my breakpoints, except the one in the Throw() method. :(
What am I doing wrong? How am I supposed to catch exceptions here?
Edit:
I do, however, hit the exception handler breakpoint when I throw the exception from within the observable, like this
private IObservable<object> Throw()
{
Debugger.Break();
return Task.Factory.StartNew(() =>
{
throw new Exception("oops");
return new object();
}).ToObservable();
}
Question modified to: "am I capable of handling an exception from within the method and not the observable?"
This is a bug in the current release of ReactiveUI - the exception thrown while creating the 'execute' observable is swallowed and the internal state of the ReactiveCommand is left in a broken state. This has been fixed in the next release.
See this github issue for details.

Cannot catch System.Reflection.TargetInvocationException (using TPL)

I have an anonymous TPL task with the following structure:
Task.Factory.StartNew(() =>
{
try
{
DoStuff();
}
catch (OperationCanceledException ex)
{
// process cancellation
}
catch (Exception ex)
{
// process (log) all exceptions
}
finally
{
// tie up various loose ends
}
},
myCancellationToken, // cancellation token
TaskCreationOptions.LongRunning, // always create a new thread
TaskScheduler.Default // default task scheduler
);
Inside of the DoStuff() function, I'm using Spring.NET Social extension for Dropbox to upload a large file to Dropbox. For some reason that I don't yet understand, an exception is being generating during the file upload (via the UploadFileAsync() method call):
(System.Net.Sockets.SocketException (0x80004005): An established connection was aborted by the software in your host machine).
I'm still working out why this exception is happening, but that's not the part that concerns me a present. The bigger problem is that the exception is ultimately wrapped by
System.Reflection.TargetInvocationException and for some strange reason, my try/catch block (in my original code snippet) isn't catching it.
Since I cannot catch the exception, it ultimately crashes the app.
Although I didn't think it should be necessary, I even tried adding an explicit catch block for TargetInvocationException, but again it never fires.
So my question is - how I do I catch this exception, and why isn't it being caught by the constructs shown in my code above?
UPDATE:
This problem appears to have nothing to do with the TPL after all. I modified the call to remove the call to StartNew() so that the code executes synchronously, and I still cannot catch this exception.
I used this code to verify that the TargetInvocationException can be caught:
[Test]
public void TaskExceptionTest()
{
var task = Task.Factory.StartNew(
() =>
{
try
{
throw new TargetInvocationException(null);
}
catch (Exception e)
{
Console.WriteLine("Caught one (inside):" + e.GetType().Name);
}
});
try
{
task.Wait();
}
catch (AggregateException ae)
{
// Assume we know what's going on with this particular exception.
// Rethrow anything else. AggregateException.Handle provides
// another way to express this. See later example.
foreach (var e in ae.InnerExceptions)
{
if (e is TargetInvocationException)
{
Console.WriteLine("After:" + e.GetType().Name);
}
else
{
throw;
}
}
}
}
You can read here about exception handling and tasks.

Catching an exception thrown in an asynchronous callback

I have a method that takes a callback argument to execute asynchronously, but the catch block doesn't seem to be catching any exceptions thrown by the synchronous call (this.Submit refers to a synchronous method).
public void Submit(FileInfo file, AnswerHandler callback)
{
SubmitFileDelegate submitDelegate = new SubmitFileDelegate(this.Submit);
submitDelegate.BeginInvoke(file, (IAsyncResult ar) =>
{
string result = submitDelegate.EndInvoke(ar);
callback(result);
}, null);
}
Is there a way to catch the exception thrown by the new thread and send it to the original thread? Also, is this the "proper" way to handle async exceptions? I wrote my code so it could be called like this (assuming the exception issue is fixed):
try
{
target.Submit(file, (response) =>
{
// do stuff
});
}
catch (Exception ex)
{
// catch stuff
}
but is there a more proper or elegant way to do this?
If you're targeting .NET 4.0, you can utilize the new Task Parallel Library, and observe the Task object's Exception property.
public Task Submit(FileInfo file)
{
return Task.Factory.StartNew(() => DoSomething(file));
}
private void DoSomething(FileInfo file)
{
throw new Exception();
}
Then use it like this:
Submit(myFileInfo).ContinueWith(task =>
{
// Check task.Exception for any exceptions.
// Do stuff with task.Result
});
where DoSomething is the method you'd like call asynchronously, and the delegate you pass to ContinueWith is your callback.
More information about exception handling in TPL can be found here: http://msdn.microsoft.com/en-us/library/dd997415.aspx
This is not a 'best practice' solution, but I think it's a simple one that should work.
Instead of having the delegate defined as
private delegate string SubmitFileDelegate(FileInfo file);
define it as
private delegate SubmitFileResult SubmitFileDelegate(FileInfo file);
and define the SubmitFileResult as follows:
public class SubmitFileResult
{
public string Result;
public Exception Exception;
}
Then, the method that actually does the file submission (not shown in the question) should be defined like this:
private static SubmitFileResult Submit(FileInfo file)
{
try
{
var submissionResult = ComplexSubmitFileMethod();
return new SubmitFileResult { Result = submissionResult };
}
catch (Exception ex)
{
return new SubmitFileResult {Exception = ex, Result = "ERROR"};
}
}
This way, you'll examine the result object, see if it has the Result or the Exception field set, and act accordingly.
In short, no.
When you call submitDelegate.BeginInvoke, it spawns the new thread, returns, and promptly exits your try/catch block (while the new thread runs in the background).
You could, however, catch all unhandled exceptions like this:
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(YourException);
This will catch everything in the application domain, however (not just your async method).

Categories

Resources