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?
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)
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.
Task.Waitbut then using TPL to begin with would be pointless. - i3arnonWaitdoes 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 withWaitis no-go, because again - it's unhandled exceptions would be trapped by TPL. - mancze