Torbjörn Kalin
Torbjörn Kalin

Reputation: 1986

Propagate exception thrown in continuation task when invoking dispatcher from unit test

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

Answers (1)

Torbj&#246;rn Kalin
Torbj&#246;rn Kalin

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

Related Questions