how to direct task exception to main thread? - c#

I am handling all of my exceptions in Dispatcher.UnhandledException so I tried to direct exceptions coming from Tasks into this event. |I tried to send exception in the main thread.
task.ContinueWith((t) =>
{
....
if (t.Exception != null)
throw t.Exception;
}, TaskScheduler.FromCurrentSynchronizationContext());
but I can not take this exception in UnhandledException event , what is wrong with my solution?
More Info , the task and continuation is happening in a RealyCommand in WPF app using MVVM Light:
here is my test view model, ExecuteTestCommand is running in UI thread
class MainViewModel : ViewModelBase
{
public MainViewModel()
{
App.Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;
}
void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
MessageBox.Show("got it");
e.Handled = true;
}
#region TestCommand
private RelayCommand _testCommand;
public RelayCommand TestCommand
{
get
{
if (_testCommand == null)
_testCommand = new RelayCommand(ExecuteTestCommand, CanExecuteTestCommand);
return _testCommand;
}
}
private bool CanExecuteTestCommand()
{
return true;
}
private void ExecuteTestCommand()
{
// throw new Exception();
Task.Factory.StartNew(() =>
{
}).ContinueWith(t =>
{
App.Current.MainWindow.Title = "It is the main UI thread" + (App.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread).ToString();
throw new NotImplementedException();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
#endregion
}

The implementation of the Task Parallel Library includes an exception handler which ensures (in most cases) that unhandled exceptions within a task do not result in the process terminating. This handler means your UnhandledException event will never be called.
When you are not using async/await, handling exceptions within a task is generally handled by the following process:
If your task is actually a sub-task of an "enclosing" task, then the "enclosing" task either propagates the exception or handles it directly (by the steps below).
If the code needs to handle exceptions, it generally registers a continuation which checks for the antecedent task entering the Faulted status, and performs an appropriate action (such as directly reporting the error to a handler of some form).
I developed the Rackspace Threading Library specifically for simplifying these scenarios. The Select and Then methods include complete handling for canceled and faulted antecedent tasks so you don't have to check the status of the antecedent in every continuation. The Select and Then methods which include a supportsErrors parameter can be used to handle a faulted task (and not propagate the error to the parent). The Finally methods can be used to handle a canceled or faulted task while still propagating the error to the parent.

If you can attach a handler to TaskScheduler.UnobservedTaskException you'll catch these exceptions too. However, note that this doesn't necessarily run on your main thread.
If you need to handle the exception on your main thread you could add code in the handler to marshal to the main thread (and then even re-throw).

I could not find any explanation why this happens?!!! but to solve my problem I used Dispatcher.Invoke
private void ExecuteTestCommand()
{
// throw new Exception();
Task.Factory.StartNew(() =>
{
}).ContinueWith(t =>
{
if (t.Exception != null)
{
App.Current.Dispatcher.Invoke(new Action(() =>
{
throw t.Exception;
}));
return;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}

Related

Terminate Window on another thread

I have a simple C# winform app where I spawn a new thread to show another winform. After a process is completed i want to close that form using the below code. The issue I have is that when I call busyForm.BeginInvoke it is bypassing the null check and throw and error. How to correctly close the winform in another thread?
static Indicator busyForm;
public static async Task Execute()
{
Thread busyIndicatorthread = new Thread(new ThreadStart(()=>FormThread()));
busyIndicatorthread.SetApartmentState(ApartmentState.STA);
busyIndicatorthread.Start();
}
private static void FormThread()
{
busyForm = new Indicator();
busyForm.Closed += (sender2, e2) => busyForm.Dispatcher.InvokeShutdown();
Dispatcher.Run();
}
public static Task Execute(){
Thread busyIndicatorthread = new Thread(new ThreadStart(()=>FormThread(hwind)));
busyIndicatorthread.SetApartmentState(ApartmentState.STA);
busyIndicatorthread.Start();
// dos some stuff
if (busyForm != null)
{
busyForm.BeginInvoke(new System.Action(() => busyForm.Close())); <--- throw null error
busyForm = null;
}
}
That is because before calling .Close() method, time has passed and it is not assured that busyForm exists anymore.
In fact, it is possible that, while the new System.Action(() => busyForm.Close() thread is starting, you main thread goes to busyForm = null;.
You can try moving the null to secondary thread.
if (busyForm != null)
{
busyForm.BeginInvoke(new System.Action(() =>
{
lock(busyForm){
busyForm.Close();
busyForm = null;
}
}));
}
Almost no application starts another message pump to display notifications. It's not needed. In all applications, the busy and progress dialog boxes are generated and displayed.by the UI thread. Operations that could block are performed in the background, eg in a background thread or far better, using async/await and Task.Run. The UI is updated using events or callbacks, eg using the Progress< T> class.
In this case though, it seems all that's needed is to display a form before a long-running task and hide it afterward:
public async void btnDoStuff_Async(object sender, EventArgs args)
{
//Disable controls, display indicator, etc
btnDoStuff.Enabled=false;
using var busyForm = new Indicator();
busyForm.Show();
try
{
var result=await Task.Run(()=> ActuallyDoStuffAndReturnResult());
//Back in the UI form
//Do something with the result
}
finally
{
//Close the busy indicator, re-enable buttons etc.
busyForm.Close();
btnDoStuff.Enabled=true;
}
}
The finally block ensures the UI is enabled and the busy form hidden even in case of error.
20+ years ago some Visual Basic 6 applications did start another Window message pump to act as a "server". Visual Basic 6 threading was very quirky, so people used various tricks to get around its limitations.
When you write this code:
busyForm.BeginInvoke(new System.Action(() => busyForm.Close())); <--- throw null error
busyForm = null;
The order in which it executes is almost certainly this:
busyForm = null;
busyForm.Close();
No wonder you're getting a null reference exception!
Simply set the form to null in your invoke. That'll fix it.
However, the correct way to do this is as Panagiotis Kanavos suggests.

How to catch an unhandled exception thrown from a continuation?

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()
);
}
Have u checked TaskScheduler.UnobservedTaskException event here.
With this you can catch unobserved exception happens in worker thread after garbage collection happens.
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
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");
}
}
private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
throw e.Exception;
}
Here's task:
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
Task.Factory.StartNew(doWork);
Thread.Sleep(2000);
GC.Collect();
GC.WaitForPendingFinalizers();
}
void doWork()
{
Thread.Sleep(1000); // Simulate work.
throw new InvalidOperationException("TEST");
}
Even if you throw your exception on UI thread, it does not mean it will eventually get to Application.ThreadException. In this case, it is intercepted by Task exception handler (handler which sets Task.Exception, Task.IsFaulted and so on), and actually becomes unobserved task exception. Think of it as Task uses Control.Invoke to execute your continuation, and Control.Invoke is synchronous, which means exception from it cannot be delivered to Application.ThreadException, because winforms does not know if it will be handled by caller or not (that's unlike Control.BeginInvoke, exceptions from which will always be delivered to Application.ThreadException).
You can verify that by subscribing to TaskScheduler.UnobservedTaskException and force garbage collection some time later after your continuation is completed.
So long story short - whether your continuation runs on UI thread on not is irrelevant in this case - all unhandled exceptions from your continuation will end as UnobservedTaskException, not ThreadException. It's reasonable to manually call your exception handler in this case and not rely on those "last resort" handlers.
Update
You have mentioned that you are using .Net 4.0. How about employ async/await feature, which wont block UI. You just need to add Microsoft Async to your project from nuget.
Now modify OnShown as below (I added code for doWork so it can be more obvious):
protected async override void OnShown(EventArgs e)
{
base.OnShown(e);
await Task.Factory.StartNew(() =>
{
Thread.Sleep(1000); // Simulate work.
throw new InvalidOperationException("TEST");
})
.ContinueWith
(
antecedent => { this.Close(); },
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext()
)
.ContinueWith
(
antecedent => {
throw antecedent.Exception;
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.FromCurrentSynchronizationContext()
);
}
Old answer
Found a solution, add task.Wait();. Not sure why it works:
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
var task = Task.Factory.StartNew(doWork)
.ContinueWith
(
antecedent => { this.Close(); },
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext()
)
.ContinueWith
(
antecedent => {
throw antecedent.Exception;
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.FromCurrentSynchronizationContext()
);
// this is where magic happens.
task.Wait();
}
I found a workaround which I will post as an answer (but I won't mark this as the answer for at least a day in case someone comes up with a better answer in the meantime!)
I decided to go old-school and use BeginInvoke() instead of a continuation. Then it seems to work as expected:
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
Task.Factory.StartNew(() =>
{
try
{
doWork();
}
catch (Exception exception)
{
this.BeginInvoke(new Action(() => { throw new InvalidOperationException("Exception in doWork()", exception); }));
}
finally
{
this.BeginInvoke(new Action(this.Close));
}
});
}
However, at least I now know why my original code wasn't working. See Evk's answer for the the reason!

Close all child WPF windows and terminate awaiting code

I am trying to implement a system for closing all modal and non-modal windows in a WPF application (with the exception of the main application window.) When these windows are closed, any code awaiting the result of a dialog should be abandoned.
So far, I've considered/attempted two strategies:
Close and restart the application.
Close all the windows and rely on task cancellation exceptions to abandon all code that was waiting for dialog results. (It bubbles up to the App level and then becomes handled.)
The first solution definitely gets the application to close and will suffice for automatic logout, but I am extremely uncomfortable with code that continues to execute after the dialog it was waiting for has been closed. Is there a nice way to stop the execution of that code?
The second solution has been working relatively well (calling code is aborted) but has one critical flaw: on occasion, some combination of modal and non-modal windows closing in quick succession will cause the application to lock up on a ShowDialog call. (At least, when you pause execution, that's where it ends up.) This is strange because breakpoints clearly demonstrate that the Closed event is being raised on all windows that I intend to close. The result that the end user sees is a login screen that cannot be clicked on but can be tabbed into. So strange! Attempts to dispatch the call at different priorities have been unsuccessful, but a Task.Delay for 100ms might have done the trick. (That's not a real solution, though.)
If each open popup is awaiting a TaskCompletionSource in the background, and, upon completion of the TCS, tries to use the dispatcher to invoke Close on itself, why would one (or more) of the dialogs still be blocking on ShowDialog, even after seeing the Closed event be raised? Is there a way to properly dispatch these calls to Close so they complete successfully? Do I need to be particular about the order in which the windows close?
Some pseudocode-C#-hybrid examples:
class PopupService
{
async Task<bool> ShowModalAsync(...)
{
create TaskCompletionSource, publish event with TCS in payload
await and return the TCS result
}
void ShowModal(...)
{
// method exists for historical purposes. code calling this should
// probably be made async-aware rather than relying on the blocking
// behavior of Window.ShowDialog
create TaskCompletionSource, publish event with TCS in payload
rethrow exceptions that are set on the Task after completion but do not await
}
void CloseAllWindows(...)
{
for every known TaskCompletionSource driving a popup interaction
tcs.TrySetCanceled()
}
}
class MainWindow : Window
{
void ShowModalEventHandler(...)
{
create a new PopupWindow and set the owner, content, etc.
var window = new PopupWindow(...) { ... };
...
window.ShowDialog();
}
}
class PopupWindow : Window
{
void LoadedEventHandler(...)
{
...
Task.Run(async () =>
{
try
await the task completion source
finally
Dispatcher.Invoke(Close, DispatcherPriority.Send);
});
register closing event handlers
...
}
void ClosedEventHandler(...)
{
if(we should do something with the TCS)
try set the TCS result so the popup service caller can continue
}
}
With Window.ShowDialog you create a nested Dispather message loop. With await, it's possible to "jump" on that inner loop and continue the logical execution of an async method there, e.g.:
var dialogTask = window.ShowDialogAsync();
// on the main message loop
await Task.Delay(1000);
// on the nested message loop
// ...
await dialogTask;
// expecting to be back on the main message loop
Now, if dialogTask gets completed via TaskCompletionSource before the corresponding Window.ShowDialog() call returns to the caller, the above code may still end up on the nested message loop, rather than on the main core message loop. E.g., this may happen if TaskCompletionSource.SetResult/TrySetCanceled is called inside the dialog's Window.Closed event handler, or right before/after Window.Close() call. This may create undesired reentrancy side effects, including deadlocks, too.
By looking at your pseudocode, it's hard to tell where the deadlock may be. What is worrying is that you're using Task.Run only to await a task that completes on the main UI thread, or to invoke a synchronous callback on the main UI thread from a pool thread (via Dispatcher.Invoke). You certainly shouldn't need Task.Run here.
I use the following version of ShowDialogAsync for a similar purpose. It makes sure any inner message loops started by nested ShowDialogAsync calls exit before this particular ShowDialogAsync task is complete:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
// testing ShowDialogAsync
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var modal1 = new Window { Title = "Modal 1" };
modal1.Loaded += async delegate
{
await Task.Delay(1000);
var modal2 = new Window { Title = "Modal 2" };
try
{
await modal2.ShowDialogAsync();
}
catch (OperationCanceledException)
{
Debug.WriteLine("Cancelled: " + modal2.Title);
}
};
await Task.Delay(1000);
// close modal1 in 5s
// this would automatically close modal2
var cts = new CancellationTokenSource(5000);
try
{
await modal1.ShowDialogAsync(cts.Token);
}
catch (OperationCanceledException)
{
Debug.WriteLine("Cancelled: " + modal1.Title);
}
}
}
/// <summary>
/// WindowExt
/// </summary>
public static class WindowExt
{
[ThreadStatic]
static CancellationToken s_currentToken = default(CancellationToken);
public static async Task<bool?> ShowDialogAsync(
this Window #this,
CancellationToken token = default(CancellationToken))
{
token.ThrowIfCancellationRequested();
var previousToken = s_currentToken;
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(previousToken, token))
{
var currentToken = s_currentToken = cts.Token;
try
{
return await #this.Dispatcher.InvokeAsync(() =>
{
using (currentToken.Register(() =>
#this.Close(),
useSynchronizationContext: true))
{
try
{
var result = #this.ShowDialog();
currentToken.ThrowIfCancellationRequested();
return result;
}
finally
{
#this.Close();
}
}
}, DispatcherPriority.Normal, currentToken);
}
finally
{
s_currentToken = previousToken;
}
}
}
}
}
This allows you to cancel the most outer modal window via the associated CancelationToken, and that would automatically close any nested modal windows (those opened with ShowDialogAsync) and exit their corresponding message loops. So, your logical execution flow would end up on the correct outer message loop.
Note it still doesn't guarantee the correct logical order of closing multiple modal windows, if that matters. But it guarantees that the tasks returned by multiple nested ShowDialogAsync calls will get completed in the correct order.
I am not sure that this is gonna fix your issue, but in my case, I created extension methods to help mixing async code and window lifetime management. For example, you can create a ShowDialogAsync() that returns task that will complete when the window is actually closed. A CancellationToken can also be provided to close the dialog automatically if you request a cancellation.
public static class WindowExtension
{
public static Task<bool?> ShowDialogAsync(this Window window, CancellationToken cancellationToken = new CancellationToken())
{
var completionSource = new TaskCompletionSource<bool?>();
window.Dispatcher.BeginInvoke(new Action(() =>
{
var result = window.ShowDialog();
// When dialog is closed, set the result to complete the returned task. If the task is already cancelled, it will be discarded.
completionSource.TrySetResult(result);
}));
if (cancellationToken.CanBeCanceled)
{
// Gets notified when cancellation is requested so that we can close window and cancel the returned task
cancellationToken.Register(() => window.Dispatcher.BeginInvoke(new Action(() =>
{
completionSource.TrySetCanceled();
window.Close();
})));
}
return completionSource.Task;
}
}
In your UI code, you would use the ShowDialogAsync() method like the following. As you can see, when the task is cancelled, the dialog is closed and an OperationCanceledException exception is thrown stopping the flow of your code.
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
YourDialog dialog = new YourDialog();
CancellationTokenSource source = new CancellationTokenSource(TimeSpan.FromSeconds(3));
await dialog.ShowDialogAsync(source.Token);
}
catch (OperationCanceledException ex)
{
MessageBox.Show("Operation was cancelled");
}
}
This is JUST for the first part of your issue (closing the windows).
if you do not need any of the results of the windows here is some simple code just to close all but the main window.
This is executing from my main window but you can change the if statement to look for your mainwindow instead if running from alternate area.
foreach(Window item in App.Current.Windows)
{
if(item!=this)
item.Close();
}
As for other threads I am unsure although as mentioned above if you have a list of handles to the threads then you should be able to traverse that as well and kill them.

Background worker doesn't catch errors from ThreadPool

I'm using a BackgroundWorker since I need to give a feedback in my UI. I also need to use ThreadPool in my class for it to run asynchronously but it seems that my background worker cannot catch the exception it throws.
It's intended to give a feedback on what task it's doing.
I made a simple application which reproduces the problem :
// MAIN UI CLASS
BackgroundWorker _bgWorker = new BackgroundWorker();
void _bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
// Returns custom error
MessageBox.Show(e.Error.Message);
}
}
void _bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
try {
// Initialize test error class
var testClass = new TestClass();
}
catch (Exception ex)
{
MessageBox.Show("im from trycatch");
}
}
private void Window_ContentRendered(object sender, EventArgs e)
{
_bgWorker.DoWork += _bgWorker_DoWork;
_bgWorker.RunWorkerCompleted += _bgWorker_RunWorkerCompleted;
}
/////////////////////////////////////////////////////////////////////////
// TEST ERROR CLASS
public TestClass()
{
throw new Exception("im a custom error");
}
This works all right. With the try-catch, it catches the error as handled by the try-catch. Whereas, without the try-catch, it handles it in RunWorkerCompleted.
However, I really need to do these methods asynchronously (which is why I use ThreadPool) but the debugger always points at the exception. (it only crashes the program when I try it without the debugger)
// TEST ERROR CLASS
public TestClass()
{
ThreadPool.QueueUserWorkItem(delegate
{
throw new Exception("im a custom error");
}
}
I tried to rethrow the error using a try-catch statement (hoping it would pass the exception to the BackgroundWorker) but it didn't work.
Hope you can help me.
Thanks in advance.
You do not need to put code on the ThreadPool from the DoWork method. The BackgroundWorker will raise your _bgWorker_DoWork method on a ThreadPool thread automatically when you call RunWorkerAsync().
If you are trying to start SEVERAL MORE ThreadPool tasks from your background worker, then I suppose that could be a consideration, but in that case I would suggest using the TPL in .NET4 if at all possible...
http://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx
If you can not use TPL, then you must wrap your ThreadPool code in a TryCatch and put the Exception some place that can be accessed outside of that method.
For example add a internal static List<Exception> backgroundExceptions; member to your UI class, the background worker add's any caught exceptions to this list, and then you must investigate this list after your background work has completed to see if there were any Exceptions present.
This is essentially what the TPL is doing for you ... giving you a place to 'observe' exceptions that happened on the background thread, but it is still your responsibility to look for those Exceptions, either in the Task.Exception property, or by calling a method that will re-throw the Exception for you.

Backgroundworker : exception during cancellation

I have a background worker which can be cancelled.
The normal flows interrupt itself when the CancelPending variable turns to true (responding to user interaction on UI which call worker.CancelAsynch() ), exceptions are thrown because if that (since normal flow is interrupted, lots of null ref exception are thrown)
So when the worker returns, I want to be able to distinguish exception that have been thrown when the worker
was canceled (to silently ignore them) from exceptions thrown when worker was not canceled (to report them to UI).
My code is as follow (sorry for the c#/vb mix ...) :
The worker class:
Public Class ClassBaseGetObjectsWorker
Inherits System.ComponentModel.BackgroundWorker
Protected Overrides Sub OnDoWork(ByVal e As System.ComponentModel.DoWorkEventArgs)
Try
Dim cpt As Int16 = 0
While cpt < 5
System.Threading.Thread.Sleep(1000)
cpt = cpt + 1
If CheckForCancellation() Then
'Simulating exception due to cancel
Throw New Exception("Exception du to cancel !")
End If
End While
Catch exc As Exception
e.Cancel = Me.CancellationPending
Throw exc
End Try
End Sub
End Class
The call back:
void setObjSetCollWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (e.Cancelled) {
resultLabel.Text += "Canceled";
//e.Error is selently ignored
}
else {
if (e.Error != null) {
//Reporting errors arising during normal (uncanceled) flow
throw e.Error.InnerException;
}
else {
//Worker process finished without error or being canceled.
updateUIAfterBackgroundJob();
}
}
}
Then, when I'm doing worker.CancelAsynch(), e.Cancelled is set to false (which is not what I expected) in the Completed call back. If I comment out "Trow exc" in the worker, if I test again, e.Cancelled is correctly set to true.
What is the cleanest way to get the information I want, that is: I want to know if the exception popping out in the completed handler was thrown when the worker was in the cancellationPending state or not?
If the best way to structure the code in your OnDoWork()-implementation is to throw an exception when you detect cancellation, do the following:
Create a CancelException:
public class CancelException: Exception {}
Throw this CancelException when you detect that cancellation is pending:
if(CheckForCancellation()) throw new CancelException();
Add a try-catch around the code in your OnDoWork()-method:
protected override void OnDoWork(DoWorkEventArgs e){
try{
//...
}
catch(CancelException){
// silently return
return;
}
}
That way your code will obey the BackgroundWorker-contract (which is to return from OnDoWork() when you detect cancellation pending, rather than to throw an exception), and the Cancelled property should now be as you expect

Categories