.NET 4.0任务:在Task.ContinueWith中将异常重新抛回UI线程

9

我有一个WPF应用程序,其中使用System.Threading.Tasks进行长时间运行的WCF调用。我通过添加处理程序到Application.Current.DispatcherUnhandledException来捕获未处理的异常。

我创建了一个任务,其中继续使用以下代码在UI线程上运行:

var task = new Task<T>(func).ContinueWith(t =>
{
    if (t.IsFaulted)
    {
        throw t.Exception.GetBaseException();
    }
    else
    {
        // Show t.Result on UI
    }
}, TaskScheduler.FromCurrentSynchronizationContext());

当任务发生异常时,我希望重新抛出异常,以便DispatcherUnhandledException处理程序可以处理异常。但是当我重新抛出异常时(如上所示),它会使我的应用程序崩溃,并且DispatcherUnhandledException不会被调用。
我该如何在UI线程上重新抛出异常,以便触发DispatcherUnhandledException处理程序?
当我使用BackgroundWorker时,重新抛出异常就可以达到这个效果。基本上,我想用Task替换BackgroundWorker,因为Task有一些很好的功能,我想要利用它们。
2个回答

9
一种解决方法是在语句中包含一个Lambda表达式,将异常重新抛出。类似于以下内容:
Exception ex = t.Exception;
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => { throw ex; }));

我不建议这样做,因为有点恶心,但它确实回答了问题!
你也可以使用SynchronizationContext.Post来执行类似的操作。

有趣...这似乎有点像黑客 :) 我想知道这是否就是 BackgroundWorker 在做的事情。 - Mas
1
这确实有点儿hacky。任务以这种方式行为的原因是ContinueWith是另一个任务,并且在其中引发的任何异常必须等待任务进行垃圾收集,然后异常才会向上冒泡。我展示的BeginInvoke可以更快地使异常冒泡。 - RichardOD
我使用了Application.Current.Dispatcher.Invoke将异常抛到我的处理程序中。感谢您提供的信息。 - Mas

2
我遇到了完全相同的问题,尽管在我的情况下,应用程序没有崩溃,但Application.DispatcherUnhandledException事件从未被触发。我还尝试使用AppDomain.CurrentDomain.UnhandledException,但也不起作用。我喜欢有一个顶层异常处理程序来处理可能发生在许多地方的全局错误。
我开发了一个使用基于事件的异步模式(EAP)的库,现在我正在将其更改为使用基于任务的异步模式(TAP),以准备好迎接基于任务的.NET 4.5具有更好的异步支持。使用EAP时,Completed事件在UI线程上执行,这很好。使用任务,您必须使用TaskScheduler.FromCurrentSynchronizationContext(),就像您上面提到的那样。我验证了ContinueWith代码正在UI线程上运行,并且我正在那里引发异常。所以,我不知道出了什么问题。
希望在.NET 4.5中能够解决这个问题,希望现在从EAP切换到TAP不会为时过早。我喜欢使用TAP可以更好地组合事物。不过,经常调用Dispatcher.Invoke()似乎有点糟糕。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接