The following code demonstrates that you have to think carefully where exceptions that happen during task execution get handled, it may not always be where you think and this can lead to unexpected behavior on the "sad code execution path" (which infrequently gets enough attention) or swallowed / unobserved exceptions, or both.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;
namespace UnitTestProject1
{
[TestClass]
public class UnitTest2
{
[TestMethod]
public async Task TestWork()
{
TaskScheduler.UnobservedTaskException += (object sender,
UnobservedTaskExceptionEventArgs e) => {
// not fired, as escalation policy doesn't kick in
Assert.IsNotNull(e.Exception);
};
var errorOccured = false;
var work = Important.Work(async () => {
// extraWork lambda
await Task.Delay(100);
throw new Exception("oops");
}, () => {
errorOccured = true;// onException func not hit
})
.ContinueWith(t => {
// otherwise ex swallowed when continuation
// exists use only-on-faulted option
Assert.IsTrue(t.IsFaulted);
//t.Wait(); // observed exception on wait
});
//work.Wait(); // exception would NOT be observed here
await work; // exception thrown here if NO continuation exists
Assert.IsFalse(errorOccured);
}
[TestMethod]
public async Task TestBetter()
{
TaskScheduler.UnobservedTaskException += (object sender,
UnobservedTaskExceptionEventArgs e) =>{
// not fired, as escalation policy doesn't kick in
Assert.IsNotNull(e.Exception);
};
var errorOccured = false;
var work = Important.BetterWork(async () =>{
// extraWork lambda
await Task.Delay(100);
throw new Exception("oops");
}, () =>{
// on onException func hit before RETHROW in BetterWork()
errorOccured = true;
})
.ContinueWith(t =>{
Assert.IsTrue(t.IsFaulted);
//t.Wait(); // RETHROWN ex observed on wait
// otherwise RETHROWN exception swallowed
// use only-on-faulted option
});
//work.Wait(); // RETHROWN exception would be observed here
// RETHROWN exception caught here if NO continuation exists
await work;
Assert.IsTrue(errorOccured);
}
public static class Important
{
public static Task Work(Func<Task> extraWork,
Action onException)
{
//
// work
//
try
{
return extraWork();
}
catch (Exception)
{
onException(); // not hit
throw;
}
}
public static async Task BetterWork(Func<Task> extraWork,
Action onException)
{
//
// work
//
try
{
await extraWork();
}
catch (Exception)
{
onException(); // might not always be hit
throw;
}
}
}
}
}