Reputation: 1986
I have the following code executing in a unit test:
var continueScheduler = new CurrentThreadScheduler();
var task = Task.Factory
.StartNew(() => { })
.ContinueWith(obj => { throw new Exception("Fail"); }, continueScheduler);
while (!task.IsCompleted)
{
DoEvents();
Thread.Sleep(10);
}
StartNew()
starts a new thread where the empty action is executed. The CurrentThreadScheduler
then makes sure that the ContinueWith()
action is made on the main thread:
public class CurrentThreadScheduler : TaskScheduler
{
private readonly Dispatcher _dispatcher;
public CurrentThreadScheduler()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
protected override void QueueTask(Task task)
{
_dispatcher.BeginInvoke(new Func<bool>(() => TryExecuteTask(task)));
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return true;
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return Enumerable.Empty<Task>();
}
}
The while
loop waits for the task (including ContinueWith()
) to finish. Here's the DoEvents()
code:
private static void DoEvents()
{
var frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
private static object ExitFrame(object frame)
{
((DispatcherFrame)frame).Continue = false;
return null;
}
The problem:
I want the Exception thrown in the ContinueWith()
action to make the test fail. The problem is that BeginInvoke()
in CurrentThreadScheduler.QueueTask()
swallows the exception and I can't figure out a way to detect it.
I have tried to subscribe to Dispatcher.CurrentDispatcher.UnhandledException
, but the event handler never gets called. I have tried to use Invoke()
instead of BeginInvoke()
hoping that the exception would propagate, but with no success.
Needless to say, the code in this question is simplified to demonstrate my problem.
Upvotes: 2
Views: 278
Reputation: 1986
OP here.
My debugging skills need to improve (among things). The problem is not Dispatcher.BeginInvoke()
, but what I do with the result.
This implementation of QueueTask
solves the problem:
protected override void QueueTask(Task task)
{
var method = new Func<bool>(delegate
{
bool success = TryExecuteTask(task);
if (task.IsFaulted)
{
throw new Exception("Invoke", task.Exception);
}
return success;
});
_dispatcher.BeginInvoke(method);
}
Now, listening to Dispatcher.CurrentDispatcher.UnhandledException
works fine:
Dispatcher.CurrentDispatcher.UnhandledException += OnDispatcherUnhandledException;
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
Assert.Fail("Exception: {0}", e.Exception);
}
That said, I agree with @Noseratio's comment that a better solution is not to use WPF Dispatcher in unit test code.
Upvotes: 2