I cannot catch an unhandled exception that is thrown from a continuation task.
To demonstrate this problem, let me show you some code that works. This code is from a basic Windows Forms application.
First, program.cs:
using System;
using System.Windows.Forms;
namespace WindowsFormsApplication3
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
Application.ThreadException += (sender, args) =>
{
MessageBox.Show(args.Exception.Message, "ThreadException");
};
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
MessageBox.Show(args.ExceptionObject.ToString(), "UnhandledException");
};
try
{
Application.Run(new Form1());
}
catch (Exception exception)
{
MessageBox.Show(exception.Message, "Application.Run() exception");
}
}
}
}
This subscribes to all the exception handlers that are available. (Only Application.ThreadException
is actually raised, but I wanted to ensure that I eliminated all other possibilities.)
Now here's the form that results in a message correctly being shown for an unhandled exception:
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication3
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
doWork();
this.Close();
}
void doWork()
{
Thread.Sleep(1000); // Simulate work.
throw new InvalidOperationException("TEST");
}
}
}
When you run this, after one second a message box will appear showing the exception message.
As you can see, I'm writing a "please wait" style form that does some background work and then automatically closes when the work is done.
Accordingly, I added a background task to OnShown()
as follows:
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
Task.Factory.StartNew(doWork).ContinueWith
(
antecedent =>
{
if (antecedent.Exception != null)
throw antecedent.Exception;
this.Close();
},
TaskScheduler.FromCurrentSynchronizationContext()
);
}
I thought that the continuation would be run in the form's context, and the exception would somehow be caught by one of the unhandled exception events I subscribed to in program.cs
.
Unfortunately nothing is caught and the form stays open with no indication that anything went wrong.
Does anyone know how I should do this, such that if an exception is thrown in the worker task and not explicitly caught and handled, it will be caught by an outer unhandled exception event?
[EDIT]
Niyoko Yuliawan suggested using await
. Unfortunately I can't use that, because this project is stuck using the ancient .Net 4.0. However, I can confirm that using await
WOULD solve it if I could use it!
For completeness, here's the - much simpler and more readable - solution that I could use if I was using .Net 4.5 or later:
protected override async void OnShown(EventArgs e)
{
base.OnShown(e);
await Task.Factory.StartNew(doWork);
this.Close();
}
[EDIT2]
raidensan also suggested a useful-looking answer, but unfortunately that doesn't work either. I think it just moves the problem around slightly. The following code also fails to cause an exception message to be shown - even though running it under the debugger and setting a breakpoint on the line antecedent => { throw antecedent.Exception; }
shows that line is being reached.
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
var task = Task.Factory.StartNew(doWork);
task.ContinueWith
(
antecedent => { this.Close(); },
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext()
);
task.ContinueWith
(
antecedent => { throw antecedent.Exception; },
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.FromCurrentSynchronizationContext()
);
}