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
}
Related
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.");
}
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.
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.
When developing winform applications it is common that you will have to just invoke to get the main GUI thread to do the GUI work.
Invoke are today obsolete(If I read correct), instead you are suppose to use the SynchronizationContext instead.
The question is : How do I handle exceptions? I have notice that sometimes the exception thrown on the "synced/invoked" thread is lost?
I do use the Application.ThreadException and AppDomain.CurrentDomain.UnhandledException but this does not help?
First of all, Synchronization Context is nothing new, it's been there since .NET 2.0. It has nothing to do specifically with exception handling. Neither does it make Control.Invoke obsolete. In fact, WinFormsSynchronizationContext, which is the WinForms' implementation of synchronization context, uses Control.BeginInvoke for Post and Control.Invoke for Send methods.
How do I handle exceptions? I have notice that sometimes the exception
thrown on the "synced/invoked" thread is lost?
There is a well-documented behavior behind "sometimes" here. Control.Invoke is a synchronous call, which propagates exceptions from inside the callback to the calling thread:
int Test()
{
throw new InvalidOperationException("Surpise from the UI thread!");
}
void Form_Load(object sender, EventArgs e)
{
// UI thread
ThreadPool.QueueUserWorkItem(x =>
{
// pool thread
try
{
this.Invoke((MethodInvoker)Test);
}
catch (Exception ex)
{
Debug.Print(ex.Message);
}
});
}
The benefit of using SynchronizationContext is in de-coupling the WinForms specifics. It makes sense for a portable library, which potentially may be used by WinForms, WPF, Windows Phone, Xamarin or any other client:
// UI thread
var uiSynchronizationContext = System.Threading.SynchronizationContext.Current;
if (uiSynchronizationContext == null)
throw new NullReferenceException("SynchronizationContext.Current");
ThreadPool.QueueUserWorkItem(x =>
{
// pool thread
try
{
uiSynchronizationContext.Send(s => Test(), null);
}
catch (Exception ex)
{
Debug.Print(ex.ToString());
}
});
Thus, with Control.Invoke (or SynchronizationContext.Send) you have an option to handle exceptions on the calling thread. You don't have such option with Control.BeginInvoke (or SynchronizationContext.Post), by design and by common sense. That's because Control.BeginInvoke is asynchronous, it queues a callback to be executed upon a future iteration of the message loop run by Application.Run.
To be able to handle exception thrown by an asynchronous callback, you'd need to actually observe the completion of the asynchronous operation. Before C# 5.0, you could have used events or Task.ContinueWith for that.
Using an event:
class ErrorEventArgs : EventArgs
{
public Exception Exception { get; set; }
}
event EventHandler<ErrorEventArgs> Error = delegate { };
void Form_Load(object sender, EventArgs e)
{
this.Error += (sError, eError) =>
// handle the error on the UI thread
Debug.Print(eError.Exception.ToString());
ThreadPool.QueueUserWorkItem(x =>
{
this.BeginInvoke(new MethodInvoker(() =>
{
try
{
Test();
}
catch (Exception ex)
{
// fire the Error event
this.Error(this, new ErrorEventArgs { Exception = ex });
}
}));
});
}
Using ContinueWith:
ThreadPool.QueueUserWorkItem(x =>
{
var tcs = new TaskCompletionSource<int>();
uiSynchronizationContext.Post(s =>
{
try
{
tcs.SetResult(Test());
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}, null);
// observe the completion,
// only if there's an error
tcs.Task.ContinueWith(task =>
{
// handle the error on a pool thread
Debug.Print(task.Exception.ToString());
}, TaskContinuationOptions.OnlyOnFaulted);
});
Finally, with C# 5.0, you can use async/await and handle exceptions thrown asynchronously with the same convenience of try/catch you have for synchronous calls:
int Test()
{
throw new InvalidOperationException("Surpise from the UI thread!");
}
async void Form_Load(object sender, EventArgs e)
{
// UI thread
var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
await Task.Run(async () =>
{
// pool thread
try
{
await Task.Factory.StartNew(
() => Test(),
CancellationToken.None,
TaskCreationOptions.None,
uiTaskScheduler);
}
catch (Exception ex)
{
// handle the error on a pool thread
Debug.Print(ex.ToString());
}
});
}
There's no automatic way for the ui thread to catch an exception from a different thread.
1) Create a method in your UI class that's designed to run on the UI thread, such as HandleExceptionFromThread(Exception ex);
2) Grab the SynchronizationContext from the ui thread. You can get that by calling SynchronizationContext.Current.
3) The method that will be run on the second thread needs to take in the SynchronizationContext as a parameter. You may need to do some dynamic casting from object to SyncrhonizationContact, but it shouldn't be too hard.
4) When an exception is caught, call uiContext.Send(HandleExceptionFromThead, ex) to synchronously, or uiContext.Post(HandleExceptionFromThead, ex) to asynchronously, send the exception to the method to be handled in the UI thread.
Here's some sample code of what I imagined.
public partial class Form1 : Form
{
.....
public void HandleExceptionFromThread(Exception ex)
{
MessageBox.Show(ex.Message);
}
public void ButtonClickToRunThread(object sender, System.EventArgs e)
{
var syncContext = SynchronizationContext.Current;
Task task = new Task((state)=>
{
SynchronizationContext uiContext = state as SynchronizationContext;
try
{
...
}
catch(Exception ex)
{
uiContext.Post(HandleExceptionFromThread, ex);
}
}, syncContext);
task.Start();
}
}
I have a task running a long time operation in WPF:
Task t = Task.Factory.StartNew(() =>
{
try
{
process(cancelTokenSource.Token, CompressionMethod, OpInfo);
}
catch (OperationCanceledException)
{
logger.Info("Operation cancelled by the user");
}
}, cancelTokenSource.Token);
try
{
t.Wait();
}
catch (AggregateException ae)
{
int i = 0;
}
private void process(CancellationToken token, CompressionLevel level, OperationInfo info)
{
// check hash
if (ComputeHash)
{
logger.Info("HASH CHECKING NOT IMPLEMENTED YET!");
MessageBox.Show(this,"HASH CHECKING NOT IMPLEMENTED YET!", "WARNING", MessageBoxButton.OK, MessageBoxImage.Warning);
}
token.ThrowIfCancellationRequested();
UserMsgPhase = "Operation finished";
return info;
}
Problem is "MessageBox.Show" throws an exception and it is not captured within "catch (AggregateException ae)". I've been reading about TPL exception handling but I don't understand why it is not catched. Please, could you help me?
Once the task is complete you can check its Exception property. You also have Status and IsCompleted properties which may be useful to you...
Check Task.Exception.
If your task is typed (returning a result), then accessing myTask.Result will throw this exception.
Moreover, if you are running .Net 4.5, you could use async/await.
As an example:
public async void MyButton_OnClick(object sender, EventArgs e)
{
try
{
Task t = ...your task...;
var myResult = await t; // do whatever you like with your task's result (if any)
}catch
{
// whatever you need
}
}
as you would do with synchronous code (but this is not an actual synchronous call)
I believe that the question's process method is a Task, so it looks like it could be implement in a different manner:
You can make the process to be implemented as Task and then you will have a task-child within task-parent.
Then you can make use of the TaskCreationOptions.AttachedToParent option.
According to Stephen Toub, using AttachedToParent will help notify children-task exception to the parent-task catch:
any exceptions from faulted children will propagate up to the parent
Task (unless the parent Task observes those exceptions before it
completes).
Example:
I've omitted the cancellation token parts in order for it to be more simple.
Task t = Task.Factory.StartNew(() =>
{
var process = new Task(() =>
{
//Copy here the process logic.
}, TaskCreationOptions.AttachedToParent);
//*Private failure handler*.
process.start();
});
try
{
t.Wait();
}
catch (AggregateException ae)
{
//handle exceptions from process.
}
In addition, you may add a private failure handler like:
//*Private failure handler*.
var failHandler = child.ContinueWith(t =>
{
//Oops, something went wrong...
}, TaskContinuationOptions.AttachedToParent|TaskContinuationOptions.OnlyOnFaulted);