Reputation: 6081
I am running some code in a separate thread, which might throw an exception (after all, code tends to do this). The thread will be spawned from the main thread (GUI) so this is where the exceptions will eventually have to be handled (like setting an error message text block). I have two solutions, but neither allows direct catching of exceptions in the GUI thread.
Note: I cannot use stuff like Task
and BackgroundWorker
(at least not out of the box) as I need to be able to change the ApartmentState
of the thread.
Here is what I would like:
var thread = new Thread(() =>
{
// Code which can throw exceptions
});
try
{
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
MethodThatAwaitsThread(thread);
}
catch
{
// Exception handling in the GUI thread
}
This does not work, as the exception never leaves the thread. I am aware that it cannot leave the thread at any time, but I am fine with waiting for the thread to end, and then catch it.
Here is my current solution, which utilizes the Dispatcher
to talk to the GUI thread:
var thread = new Thread(() =>
{
try
{
// Code which can throw exceptions
Dispatcher.Invoke(UpdateGuiAsSuccess);
}
catch (Exception ex)
{
Dispatcher.Invoke(UpdateGuiAsError);
}
}
An alternative solution is to store the Exception
in an object and then explicitely check for it afterwards. But this comes at a risk of people forgetting to check the exception:
Exception ex = null;
var thread = new Thread(() =>
{
try
{
// Code which can throw exceptions
}
catch (Exception threadEx)
{
ex = threadEx;
}
}
if (ex != null)
{
UpdateGuiAsError();
}
else
{
UpdateGuiAsSuccess();
}
Is there anyway I can get the error to be re-thrown in the GUI thread, once the worker thread dies?
Upvotes: 2
Views: 511
Reputation: 109567
You can use Task
with an STA thread (which I assume is what you want).
To do so, you can write some helper methods to start a task on a thread that has been set to STA:
public static class STATask
{
/// <summary>
/// Similar to Task.Run(), except this creates a task that runs on a thread
/// in an STA apartment rather than Task's MTA apartment.
/// </summary>
/// <typeparam name="TResult">The return type of the task.</typeparam>
/// <param name="function">The work to execute asynchronously.</param>
/// <returns>A task object that represents the work queued to execute on an STA thread.</returns>
[NotNull] public static Task<TResult> Run<TResult>([NotNull] Func<TResult> function)
{
var tcs = new TaskCompletionSource<TResult>();
var thread = new Thread(() =>
{
try
{
tcs.SetResult(function());
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
}
/// <summary>
/// Similar to Task.Run(), except this creates a task that runs on a thread
/// in an STA apartment rather than Task's MTA apartment.
/// </summary>
/// <param name="action">The work to execute asynchronously.</param>
/// <returns>A task object that represents the work queued to execute on an STA thread.</returns>
[NotNull] public static Task Run([NotNull] Action action)
{
var tcs = new TaskCompletionSource<object>(); // Return type is irrelevant for an Action.
var thread = new Thread(() =>
{
try
{
action();
tcs.SetResult(null); // Irrelevant.
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
}
}
Once you have that, you can easily create an STA task and then use .ContinueWith()
to handle exceptions thrown in the task, or use await
to catch the exceptions.
(Note: [NotNull]
is from Resharper annotations - remove them if you're not using Resharper.)
Upvotes: 6
Reputation: 488
ExceptionDispatchInfo is a good solution for rethrowing exceptions in a different thread since it preserves the original stacktrace.
ExceptionDispatchInfo exceptionInfo = null;
var thread = new Thread(() =>
{
try
{
// Code which can throw exceptions
}
catch (Exception ex)
{
exceptionInfo = ExceptionDispatchInfo.Capture(ex);
}
});
try
{
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
MethodThatAwaitsThread(thread);
exceptionInfo?.Throw();
}
catch
{
// Exception handling in the GUI thread
}
Upvotes: 1