3

My problem: I want to use TPL in WinForms application under .NET 4 and I need the task continuations to elevate any unhandled exceptions immediately ("fast throw") instead of waiting for GC collecting the Task. Is it possible?

In .NET 4.5 with async/await support it is possible to write:

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

The exception in continuation would be thrown immediately if not handled in Excecute method.

How to achieve same behavior in .NET 4 without async/await support?

2 답변


1

First of all, you should know it's possible to use async-await with .Net 4.0 with Microsoft.Bcl.Async

But without it, you can add a continuation to the task with ContinueWith and have it run only when there was an exception with TaskContinuationOptions.OnlyOnFaulted

Me.LongWork().ContinueWith(Sub(task) MsgBox(task.Exception.Message), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted)


  • That does not answer my question - I wanted to "fast-throw" any unhandled exceptions. Maybe I introduced some confusion because of the example with handler. But imagine there was no handler. Or it is always possible that any exception handler will raise exception itself. I want those exceptions to be thrown immediately without TPL swallowing them as UnobservedTaskException. - mancze
  • @mancze thrown where? You want to tear down the application? - i3arnon
  • To elevate and bubble the exception through the whole call stack. There might be my custom handlers on the way which will handle it properly. Without handlers tearing down app, yes. - mancze
  • @mancze There's no call stack on a task to bubble up on as long as there's no thread waiting for it. You can just block on the task with Task.Wait but then using TPL to begin with would be pointless. - i3arnon
  • I see, you've got point about the task having no specific call stack on it's own. Unless I schedule task to the specific thread (ui thread). Calling Wait does the trick but blocks the ui thread which I do not want to. I want to "Wait" as soon, as exception is thrown. Having continuation with Wait is no-go, because again - it's unhandled exceptions would be trapped by TPL. - mancze

1

1) It is either possible to use Microsoft.Bcl.Async as i3arnon suggested.

2) Or if you do not want to reference extra libraries I have came up with the solution based on the async/await. The magic behind is yucky but it's best I've got.

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

It meets the requirements - exception is thrown "fast" and is can be handled. The exception is Posted to the target SynchronizationContext thus escaping the TPL's exception trapping mechanism. It's far from fast and synchronous but at least it behaves better than waiting for task disposal.

Related

Latest