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 Post
ed 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.Wait
but then using TPL to begin with would be pointless. - i3arnonWait
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 withWait
is no-go, because again - it's unhandled exceptions would be trapped by TPL. - mancze