Application being closed even with exceptions treatment - c#

I have an action trigged by a button that should cover every possible cases.
private async void btnStart_Click(object sender, EventArgs e)
{
try
{
btnStart.Enabled = false;
await Task.Delay(1000);
btnStart.Visible = false;
btnStop.Visible = true;
var maxSessions = numericFieldSessions.Value;//to run the same stuff in parallell
for (var i = 0; i < maxSessions; i++)
{
await Task.Run(() =>
{
Parallel.Invoke(async () =>
{
while (true)
{
try
{
A();
await Task.Run(() => { B(); }); //longer operation
}
catch (CustomExceptionA ex)
{
DoLog($"Custom Exception A: {ex.Message}");
}
catch (CustomExceptionB ex)
{
DoLog($"Custom Exception B: {ex.Message}");
}
catch (CustomExceptionC ex)
{
DoLog($"Custom Exception C: {ex.Message}");
}
catch (Exception ex)
{
DoLog($"Generic Exception: {ex.Message}");
}
}
});
});
}
}
catch (Exception ex)
{
DoLog($"Full Generic Exception: {ex.Message}");
}
}
DoLog() only writes the string to a File.
After a long time, the program just crash. Without logging anything. I saw in the Windows Event Log that an unhandled exception was thrown inside the method B(). But B() itself should not handle errors... and it isn't!
This is the log:
System.Runtime.InteropServices.ExternalException
em System.Drawing.Image.FromHbitmap(IntPtr, IntPtr)
em System.Drawing.Image.FromHbitmap(IntPtr)
em System.Drawing.Icon.BmpFrame()
em System.Drawing.Icon.ToBitmap()
em System.Windows.Forms.ThreadExceptionDialog..ctor(System.Exception)
em System.Windows.Forms.Application+ThreadContext.OnThreadException(System.Exception)
em System.Windows.Forms.Control.WndProcException(System.Exception)
em System.Windows.Forms.Control+ControlNativeWindow.OnThreadException(System.Exception)
em System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
And right after this error event there is another (in the same second):
Faulting application name: MyApp.exe, version: 1.0.0.0, timestamp: 0xb5620f2c
Faulty module name: KERNELBASE.dll, version: 10.0.18362.476, timestamp: 0x540698cd
Exception code: 0xe0434352
Fault offset: 0x001135d2
Failed process ID: 0xf54
Failed application start time: 0x01d5da61843fe0f8
Faulting application path: PATH_TO_MY_APP.exe
Faulting module path: C:\Windows\System32\KERNELBASE.dll
Report ID: 120a68ca-a077-47a4-ae62-213e146956a6
Failed package full name:
Application ID for the failed package:
How to prevent this? I thought that every exception would be handled.. how to prevent this? Assuming that - anything that happens inside B() should be handled outside it?

From Peter Torr's post on Async and Exceptions in C# he makes the following suggestion when dealing with exception handling in async methods:
Basically, in order to be safe you need to do one of two things:
Handle exceptions within the async method itself; or
Return a Task and ensure that the caller attempts to get the result whilst also handling exceptions (possibly in a parent stack frame)
Failure to do either of these things will result in unwanted behaviour.
Reproducing the Error
Because I don't know the method sugnature of your b method, I started with the basic void b(). Using void b() I was unable to reproduce your error in the following snippet:
private async void button1_Click(object sender, EventArgs e)
{
try
{
await Task.Run(() => { b(); });
//also tried:
//await Task.Run(b);
//await Task.Run(new Action(b));
}
catch (Exception E)
{
MessageBox.Show($"Exception Handled: \"{E.Message}\"");
}
}
void b()
{
DateTime begin = DateTime.Now;
while (DateTime.Now.Subtract(begin).TotalSeconds < 3) //Wait 3 seconds
{ /*Do Nothing*/ }
//c() represents whichever method you're calling
//inside of b that is throwing the exception.
c();
}
void c()
{
throw new Exception("Try to handle this exception.");
}
In this case, VS did break when the exception is thrown highlighting the throwing line claiming it was an user-unhandled exception, however, continuing execution did catch the exception and the message box was shown. Running the example without the debugger caused no breaks and the MessageBox was shown as expected.
Later on I tried changing the b method and making it an async void:
async void b()
{
await Task.Run(() =>
{
DateTime begin = DateTime.Now;
while (DateTime.Now.Subtract(begin).TotalSeconds < 10) //Wait 10 seconds
{ /*Do Nothing*/ }
});
c();
}
In this scenario, where b is async, I was able to reproduce your error. Visual Studio's debugging still informs me of the exception as soon as it is thrown by highlighting the throwing line, however, continuing execution now breaks the program, and the try-catch block was unable to catch the exception.
This probably happens because async void defines a "Fire-and-Forget" pattern. Even though you're calling it through Task.Run(), the await before Task.Run() IS NOT getting the result of b() because it is still void. This causes the Exception to be left unused until the GC tries to collect it
In Peter Torr's words:
The basic reason for this is that if you don't attempt to get the result of a Task (either by using await or by getting the Result directly) then it just sits there, holding on to the exception object, waiting to get GCed. During GC, it notices that nobody ever checked the result (and therefore never saw the exception) and so bubbles it up as an unobserved exception. As soon as someone asks for the result, the Task throws the exception instead which must then be caught by someone.
The Solution
What solved the issue for me was changing the signature of void b() to async Task b(), also, after this change, instead of calling b through Task.Run() you can now just call it directly with await b(); (see Solution 1 below).
If you have access to b's implementation, but for some reason can't change its signature (for instance, to maintain backwards compatbility), you'll have to use a try-catch block inside of b, but you can't re-throw any exceptions you catch, or the same error will continue (see Solution 2 below).
Solution 1
Change b's signature:
private async void button1_Click(object sender, EventArgs e)
{
//Now any exceptions thrown inside b, but not handled by it
//will properly move up the call stack and reach this level
//where this try-catch block will be able to handle it.
try
{
await b();
}
catch (Exception E)
{
MessageBox.Show($"Exception Handled: \"{E.Message}\"");
}
}
async Task b()
{
await Task.Run(()=>
{
DateTime begin = DateTime.Now;
while (DateTime.Now.Subtract(begin).TotalSeconds < 3) //Wait 3 seconds
{ /*Do Nothing*/ }
});
c();
}
void c()
{
throw new Exception("Try to handle this exception.");
}
Solution 2
Change b's body:
private async void button1_Click(object sender, EventArgs e)
{
//With this solution, exceptions are treated inside b's body
//and it will not rethrow the exception, so encapsulating the call to b()
//in a try-catch block is redundant and unecessary, since it will never
//throw an exception to be caught in this level of the call stack.
await Task.Run(() => { b(); });
}
void b()
{
DateTime begin = DateTime.Now;
while (DateTime.Now.Subtract(begin).TotalSeconds < 3) //Wait 3 seconds
{ /*Do Nothing*/ }
try
{
c();
}
catch (Exception)
{
//Log the error here.
//DO NOT re-throw the exception.
}
}
void c()
{
throw new Exception("Try to handle this exception.");
}

Related

Handling exception from non-awaited Task [duplicate]

This question already has an answer here:
Exception handling in fire and forget for C# 5 (in .net 4.5)
(1 answer)
Closed 2 years ago.
Let's assume I have a console application with Main method, something like this:
public static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
{
Console.WriteLine("App Unobserved");
};
TaskScheduler.UnobservedTaskException += (sender, eventArgs) =>
{
Console.WriteLine("Task Unobserved");
};
Task.Run(async () => await MyAwesomeMethod());
// other awesome code...
Console.ReadLine();
}
public static async Task MyAwesomeMethod()
{
// some useful work
if (something_went_wrong)
throw new Exception();
// other some useful work
}
So, I just run MyAwesomeMethod (fire-and-forget), and want to do some other job, but I also want to know if there any unhandled exceptions. But application finishes successfully without any sign of problem (exception is just swallowed).
How can I handle exception from MyAwesomeMethod(), without awaiting it or using Task.Run(...).Wait()?
So, I just run MyAwesomeMethod (fire-and-forget)... but I also want to know if there any unhandled exceptions. But application finishes successfully without any sign of problem (exception is just swallowed).
That's not "fire and forget", then. "Fire and forget" literally means that you don't care when (or whether) the task completes (or errors).
How can I handle exception from MyAwesomeMethod(), without awaiting it or using Task.Run(...).Wait()?
Use await anyway:
Task.Run(async () => {
try {
await MyAwesomeMethod();
} catch (Exception ex) {
Console.WriteLine(ex);
}
});
You can check the status of your task once it's done.
Task.Run(() => MyAwesomeMethod()).ContinueWith((task) =>
{
if (task.Status == TaskStatus.RanToCompletion && task.Result != null)
{
}
else
{
try
{
Logger.LogError(task.Exception.ToString());
Logger.LogMessage("something_went_wrong");
}
catch { }
}
});
You could for example wrap the code in the background task in a try...catch block and raise an event as soon as you enter the catch block (if you do).
Like
event EventHandler<Exception> exceptionInWorker;
and in the task do
try
{
//do something
}
catch (Exception e)
{
exceptionInWorker?.Invoke(this, e);
}
You can subscribe to TaskScheduler.UnobservedTaskException event as you do but with a handler that takes UnobservedTaskExceptionEventArgs as its second parameter, through it you could access the unhandled exception via its Exception property and log all info about it.

Looking for what caused the "A Task's exception(s) were not observed..."

First of all, let me say that, it's so hard to explain my problem in detail, but I will try my best. I will update with a detailed explanation or more codes which is used by me that probably caused the exception. And I'm sorry if my code is messy.
There are many SO questions with the same title that I have read, but I have no luck at all. I have a very little understanding of Thread/Task/Dispatcher here, so please guide me if you find something wrong about my code.
Intoduction
My application does the background task by timer every n minutes.
The background task: fetches data from API, then generates Window element as a form to contain the data, then prints them.
The problem: The exception has already occured twice at the moment, which prevents two form documents to be generated so they are not printed.
The detailed exception which is get from TaskScheduler.UnobservedTaskException is:
A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.
Stack Trace: N/A
Inner Exception:
System.Collections.ObjectModel.ReadOnlyCollection`1[System.Exception]
Here is my piece of code that may be useful for you to find the source of the problem:
public void BackgroundTask(object sender, EventArgs e)
{
Application.Current.Dispatcher.Invoke(
new Action(GetInvoiceData),
DispatcherPriority.Background,
null
);
}
...where GetInvoiceData is:
public async void GetInvoiceData()
{
try
{
JsonData = await ApiHelperInstance.Post(ApiParam);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
if (!string.IsNullOrEmpty(JsonData))
{
var apiReturn = new ApiReturn();
try
{
apiReturn = JsonConvert.DeserializeObject<ApiReturn>(JsonData);
}
catch (JsonException ex)
{
Console.WriteLine(ex.Message);
}
finally
{
if (apiReturn.Result != null)
{
foreach (ApiResult apiResult in apiReturn.Result)
{
InvoiceQueue.Enqueue(new Invoice(apiResult));
}
var worker = new BackgroundWorker();
worker.DoWork += GenerateDocumentAndPrint;
worker.RunWorkerAsync();
}
}
}
}
}
...and GenerateDocumentAndPrint is:
public void GenerateDocumentAndPrint(object sender, DoWorkEventArgs e)
{
while (InvoiceQueue.Count != 0)
{
Dispatcher.Invoke(() =>
{
Invoice invoice = InvoiceQueue.Dequeue();
var invoiceForm = new InvoiceForm();
var shippingLabelForm = new ShippingLabelForm();
invoiceForm.Dispatcher.Invoke(async () =>
{
var invoiceTmp = invoice;
var invoiceDoc = new FixedDocument();
try
{
invoiceDoc = await invoiceForm.CreateDocument(invoiceTmp);
}
finally
{
InvoiceDocumentName = PrintJobNameSub + " - Invoice #" + invoice.TransOrder.TransNumber;
PrintHelperInstance.SetPrinterByName(InvoicePrinterName);
PrintHelperInstance.PrintDocument(invoiceDoc.DocumentPaginator, InvoiceDocumentName);
invoiceForm.Close();
}
}, DispatcherPriority.ContextIdle);
shippingLabelForm.Dispatcher.Invoke(async () =>
{
var invoiceTmp = invoice;
var shippingLabelDoc = new FixedDocument();
try
{
shippingLabelDoc = await shippingLabelForm.CreateDocument(invoiceTmp);
}
finally
{
ShippingLabelDocumentName = PrintJobNameSub + " - Shipping Label #" + invoice.TransOrder.TransNumber;
PrintHelperInstance.SetPrinterByName(ShippingLabelPrinterName);
PrintHelperInstance.PrintDocument(shippingLabelDoc.DocumentPaginator, ShippingLabelDocumentName);
shippingLabelForm.Close();
}
}, DispatcherPriority.ContextIdle);
}, DispatcherPriority.Normal);
}
}
...and async method CreateDocument from both of InvoiceForm and ShippingLabelForm contains await Task.Delay(delay).
Is there any mistake I made from my code? Is it caused by wrong use of Dispatcher.Invoke? Is it caused by wrong use of DispatcherPriority enum? Is it something wrong with the Task.Delay operation?
TaskScheduler.UnobservedTaskException gets exceptions when a task's exceptions are not observed. If you await all your tasks, then this event will never fire.
Note that this event does not necessarily mean an error in the strict sense of the term. For example, this can happen if you abandon a task - fairly common if your code contains any Task.WhenAny calls. This can also happen if a "fire and forget" task throws an exception. In both of these cases, it's not actually an error. In the WhenAny case, a different task already completed the Task.WhenAny, so you don't care if another task threw an exception. In the case of "fire and forget", "forget" literally means "I don't care about exceptions", so you shouldn't care if it threw an exception.
This event only indicates an error if you are accidentally missing an await. The easiest way to find a missing await is by examining the call stack of the inner exceptions and then examining the callers of that method, etc., until you find the one that is not properly awaiting the task.

How to ignore a specific Exception INSTANCE for an async method in Visual Studio (break on thrown)

If I set VS to break on thrown exceptions of a given type, and the debugger haltet at some piece of code, I look for a possibility to continue running (F5) and not break on each parent stack frame of the same exception.
In other words, I want a possibility to ignore the very instance of the exception for the rest of its stackframe (assuming it is caught somewhere above).
The only way I found is tedious (and error-prone if such exceptions are thrown often in some situation): Uncheck the "break when this type is thrown" checkbox, continue execution, immediately reactivate the exception in the Exception Settings pane.
Edit: clarifying to 1st answer
I forgot to mention (as I was not aware that this is relevant info) that I'm in async methods. I modified your example to async, like this:
public static class Program
{
public static void Main(string[] args)
{
try
{
Alpha();
}
catch (Exception e) // don't try this at home kids
{
// we should never get here in this example
throw;
}
}
private static async Task Alpha()
{
try
{
await Bravo(); // *** 4 ***
}
catch (Exception e)
{
// debugger won't stop here because we didn't re-throw
}
}
private static async Task Bravo()
{
try
{
await Tango(); // *** 2 ***
}
catch (Exception) // don't try this at home kids
{
throw; // *** 3 *** debugger will stop here again because we are re-throwing
}
}
private static async Task Tango()
{
var x = 1;
var y = 0;
var c = x / y; // *** 1 ***
}
}
And the debugger stops at all four marked spots in the numbered order. So it does not only stop at rethrows, but also at all awaits inbetween. And for deep call hierarchies, this is disturbing my debug-flow, so to speak.
So to summarize, I'd need a solution for async calls to not re-break at every await between the exception-causing stackframe and the one where the exception is eventually caught.
Possible?
With the new info that became apparent in the question, this answer may no longer be suitable. :)
If I set VS to break on thrown exceptions of a given type, and the debugger haltet at some piece of code, I look for a possibility to continue running (F5) and not break on each parent stack frame of the same exception....
The only way I found is tedious (and error-prone if such exceptions are thrown often in some situation): Uncheck the "break when this type is thrown" checkbox, continue execution, immediately reactivate the exception in the Exception Settings pane
Without seeing your code I can only assume you are re-throwing the same exception.
Consider the following code:
class Program
{
static void Main(string[] args)
{
try
{
Alpha();
}
catch (Exception e) // don't try this at home kids
{
// we should never get here in this example
throw;
}
}
private static void Alpha()
{
try
{
Bravo();
}
catch (Exception e)
{
// debugger won't stop here because we didn't re-throw
}
}
private static void Bravo()
{
try
{
Tango();
}
catch (Exception) // don't try this at home kids
{
throw; // debugger will stop here again because we are re-throwing
}
}
private static void Tango()
{
var x = 1;
var y = 0;
var c = x / y;
}
}
With the debugger set to break when System.DivideByZeroException is thrown, the debugger will:
stop first in Tango where it is attempts to divide by zero and the first exception is thrown
stop again in the catch() handler in Bravo where the exception is thrown again
But it will not stop in Alpha because I silently eat the exception.
The behaviour you are seeing is by design.

TaskCanceledException with ContinueWith

I've been trying to figure out why I'm getting a TaskCanceledException for a bit of async code that has recently started misbehaving. I've reduced my issue down to a small code snippet that has me scratching my head:
static void Main(string[] args)
{
RunTest();
}
private static void RunTest()
{
Task.Delay(1000).ContinueWith(t => Console.WriteLine("{0}", t.Exception), TaskContinuationOptions.OnlyOnFaulted).Wait();
}
As far as I'm aware, this should simply pause for a second and then close. The ContinueWith won't be called (this only applies to my actual use-case). However, instead I'm getting a TaskCanceledException and I've no idea where that is coming from!
You are using the wrong taskcontinuationoption:
See following link : https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions%28v=vs.110%29.aspx
It says :
Specifies that the continuation task should be scheduled only if its antecedent threw an unhandled exception. This option is not valid for multi-task continuations.
As guys said above this call requires just antecedent-task in faulted-status otherwise will throw TaskCanceledException, for this concrete case you can generalize ContinueWith to process all statuses:
await Task.Delay(1000).ContinueWith(
task =>
{
/* take into account that Canceled-task throw on next row the TaskCancelledException */
if (!task.IsFaulted) {
return;
}
Console.WriteLine("{0}", task.Exception);
// do smth like 'throw task.Exception.InnerException'
});
I also received this error:
The block of code looked like this:
private void CallMediator<TRequest>(TRequest request) where TRequest : IRequest<Unit>
{
_ = Task.Run(async () =>
{
var mediator = _serviceScopeFactory.CreateScope().ServiceProvider.GetService<IMediator>()!;
await mediator.Send(request).ContinueWith(LogException, TaskContinuationOptions.OnlyOnFaulted);
});
}
private void LogException(Task task)
{
if (task.Exception != null)
{
_logger.LogError(task.Exception, "{ErrorMessage}", task.Exception.Message);
}
}
Reading the documentation for the ContinueWith method, it has the following remarks:
The returned Task will not be scheduled for execution until the current task has completed. If the continuation criteria specified through the continuationOptions parameter are not met, the continuation task will be canceled instead of scheduled.
So for me, it called the first task (mediator.Send(request)), then it continued with the task ContinueWith(...), which is the one I awaited. However, since an exception had not occurred in the first task, the second task was cancelled. Therefore, when awaiting the second task, it threw a TaskCanceledException.
What I did, was to change the code to this:
private void CallMediator<TRequest>(TRequest request) where TRequest : IRequest<Unit>
{
_ = Task.Run(async () =>
{
var mediator = _serviceScopeFactory.CreateScope().ServiceProvider.GetService<IMediator>()!;
try
{
_ = await mediator.Send(request);
}
catch (Exception ex)
{
_logger.LogError(ex, "{ErrorMessage}", ex.Message);
}
});
}
Instead of using .ContinueWith(...), I have replaced it with just a regular try-catch block in case of the task I am interested in fails. I think this simplifies the code and makes it more readable.
In the question, there is this line of code:
Task.Delay(1000).ContinueWith(t => Console.WriteLine("{0}", t.Exception), TaskContinuationOptions.OnlyOnFaulted).Wait();
I would rewrite it to:
try
{
Task.Delay(1000).Wait();
}
catch (Exception ex)
{
Console.WriteLine("{0}", ex);
}

How to propagate thread exceptions back to application exception handler

http://msdn.microsoft.com/en-us/magazine/gg598924.aspx
Why exceptions are not propagated by WPF Dispatcher.Invoke?
How can I allow Task exceptions to propagate back to the UI thread?
In the code below I need to propagate execeptions that are thrown in the tasks and their continuations back up to the ui thread where they will be handled by LogException. If I need to re-throw an exception somewhere along the line thats fine with me. Whatever works. How do I do that?
I referenced some questions that are similar to mine but I do not see an answer that is relevant to my app.
Edit 3: posted a simplified example
Edit 2:
See this:
http://msdn.microsoft.com/en-us/library/dd997415(v=vs.100).aspx
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
FireAndForget();
WaitOnTask();
}
private void FireAndForget()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
throw new Exception("boo");
});
Task c1 = t1.ContinueWith((t) =>
{
// The app global exception handler will not catch this.
}, TaskContinuationOptions.OnlyOnFaulted);
//MessageBox.Show("Task is running");
}
private void WaitOnTask()
{
Task t1 = Task.Factory.StartNew(() =>
{
throw new Exception("boo");
});
try
{
t1.Wait();
}
catch (Exception ex)
{
// The app global exception handler will catch this:
throw new Exception("Task", ex);
}
}
}
public partial class App : Application
{
public App()
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Application.Current.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(Current_DispatcherUnhandledException);
//System.Threading.Tasks.TaskScheduler.UnobservedTaskException += new EventHandler<System.Threading.Tasks.UnobservedTaskExceptionEventArgs>(TaskScheduler_UnobservedTaskException);
}
void TaskScheduler_UnobservedTaskException(object sender, System.Threading.Tasks.UnobservedTaskExceptionEventArgs e)
{
LogException(e.Exception);
}
void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
LogException(e.Exception);
}
void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
LogException(e.ExceptionObject as Exception);
}
private void LogException(Exception ex)
{
// log it
string error = "This app has encountered an unexpected error . The error message is:" + Environment.NewLine + ex.Message + Environment.NewLine;
Exception tmp = ex.InnerException;
while (tmp != null)
{
error += "Inner exception is: " + Environment.NewLine + tmp.Message + Environment.NewLine;
tmp = tmp.InnerException;
}
error += "Please press OK to exit.";
MessageBox.Show(error, "Error");
Environment.Exit(-1);
}
}
When you use StartNew or ContinueWith, any exceptions are placed on the returned Task.
There are two problems with marshaling exceptions:
Task.Exception wraps your exception in an AggregateException.
When you throw an exception later (e.g., on another thread), the original call stack is lost.
For the first problem, some people use the Flatten or Handle members to work directly with AggregateException. I prefer unwrapping the exceptions by dealing with Task.Exception.InnerException instead of Task.Exception.
For the second problem, some people work around it by wrapping it in another exception, but I have taken an alternative approach. .NET 4.5 introduced ExceptionDispatchInfo, which is the correct way to do this. In .NET 4.0 you can hack something like this:
public static Exception Rethrow(this Exception ex)
{
typeof(Exception).GetMethod("PrepForRemoting",
BindingFlags.NonPublic | BindingFlags.Instance)
.Invoke(ex, new object[0]);
throw ex;
}
I'm not sure if i'm missing something here, but if you use
TaskScheduler.FromCurrentSynchronizationContext() as the second parameter to ContinueWith
then it will be marshaled back onto your UX thread.
I actually wrote a blog post about it if you want a little more of a sample.
http://www.briankeating.net/post/Why-I-love-the-Task-library
Kr,
Brian.
The answer to the question is found here:
http://blogs.msdn.com/b/pfxteam/archive/2009/05/31/9674669.aspx
Basically there are two scenarios: Situations where you can wait on the task and situations where you cannot i.e. fire and forget.
In situations where you can wait on the task, wrap it in a try block as shown in the question and rethrow the error. The global app handler will catch it.
In situtions where you cannot wait on the task you have to call your logger manually. There is no application level handler that will catch the error. There is a possibility that TaskScheduler.UnobservedTaskException will fire, however that event is IMHO highly circumstantial and fragile and not a good option.
To propagate the exceptions in your code you need to Wait on all the tasks. If you make the following changes to your FireAndForget method the Exception in the nested Task will be propagated back to the calling thread.
private void FireAndForget()
{
var tasks = new Task[2];
tasks[0] = Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
throw new Exception("boo");
});
tasks[1] = tasks[0].ContinueWith((t) =>
{
throw new Exception("nested boo", tasks[0].Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
try
{
Task.WaitAll(tasks);
}
catch (AggregateException ex)
{
throw new Exception("Task", ex);
}
}
Of course this is no longer a "fire and forget" method. If waiting on the tasks is undesirable you will need to write to your log file from within the continuation.
You can await the completion of the task to receive exception from the task code.
try{
await Task.Factory.StartNew(() => throw Exception("hello"));
}catch{
// will get exception here
}

Categories