在 TPL 中快速抛出未处理的异常

Fast throw unhandled exceptions in TPL

我的问题:我想在 .NET 4 下的 WinForms 应用程序中使用 TPL,并且 我需要任务继续以立即提升任何未处理的异常 ("fast throw") 而不是等待 GC 收集 Task. 可能吗?

在支持 async/await 的 .NET 4.5 中,可以这样写:

Public Class AwaitForm
    Inherits Form

    Private Async Sub Execute()
        Dim uiScheduler = TaskScheduler.FromCurrentSynchronizationContext()

        Try
            Await Me.LongWork().
                ContinueWith(Sub(t) Me.LongWorkCompleted(), uiScheduler)

        Catch ex As Exception
            ' yay, possible to handle here
            ' eg. MsgBox(ex.Message)
            Throw
        End Try
    End Sub

    Private Async Function LongWork() As Task
        Await Task.Delay(1000)
    End Function

    Private Sub LongWorkCompleted()
        Throw New Exception("Ups")
    End Sub

End Class

如果不在Excecute方法中处理,continuation中的异常会立即抛出。

如何在没有 async/await 支持的情况下在 .NET 4 中实现相同的行为?

首先,您应该知道在 .Net 4.0 Microsoft.Bcl.Async

中可以使用 async-await

但是如果没有它,您可以使用 ContinueWith 为任务添加一个延续,并且仅当 TaskContinuationOptions.OnlyOnFaulted

出现异常时才使用它 运行
Me.LongWork().ContinueWith(Sub(task) MsgBox(task.Exception.Message), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted)

1) 可以按照 i3arnon 的建议使用 Microsoft.Bcl.Async

2) 或者,如果您不想引用额外的库,我已经提出了基于 async/await 的解决方案。背后的魔力令人讨厌,但这是我所拥有的最好的。

Imports System.Reflection
Imports System.Runtime.CompilerServices
Imports System.Threading


Public Module TaskExtensions

    ''' <summary>Throws the exception on the current SynchronizationContext or ThreadPool if there is none.</summary>
    ''' <param name="task">Task whose faulted continuation should throw exception.</param>
    <Extension()>
    Public Sub ThrowOnFaulted(task As Task)
        Dim context = SynchronizationContext.Current
        ThrowOnFaulted(task, context)
    End Sub


    ''' <summary>Throws the exception on the ThreadPool in given context.</summary>
    ''' <param name="task">Task whose faulted continuation should throw exception.</param>
    ''' <param name="targetContext">The target context on which to propagate the exception. Null to use the ThreadPool.</param>
    <Extension()>
    Public Sub ThrowOnFaulted(task As Task, targetContext As SynchronizationContext)
        task.ContinueWith(Sub(t) ThrowOnFaultedCore(t, targetContext), TaskContinuationOptions.OnlyOnFaulted)
    End Sub


    ''' <remarks>Taken from System.RunTime.CompilerServices.AsyncServices.</remarks>
    Private Sub ThrowOnFaultedCore(task As Task, targetContext As SynchronizationContext)
        Dim exception = task.Exception

        If targetContext IsNot Nothing Then
            Try
                targetContext.Post(Sub(state) Throw DirectCast(state, Exception), exception)
                Return
            Catch ex As Exception
                exception = New AggregateException({exception, ex})
            End Try
        End If

        ThreadPool.QueueUserWorkItem(Sub(state) Throw DirectCast(state, Exception), exception)
    End Sub

End Module

它符合要求 - 抛出异常 "fast" 并且可以处理。异常被 Posted 到目标 SynchronizationContext 从而逃避了 TPL 的异常捕获机制。它远谈不上快速和同步,但至少它比等待任务处理表现得更好。