You're describing the behavior as it was in .NET 4, but it will be difficult for you to force the garbage collection and actually observe that behavior. The following quote from Stephen Toub's excelent write-up on the subject should make it even more clear:
Tasks keep track of whether an unhandled exception has been
“observed.” In this context, “observed” means that code has joined
with the Task in some fashion in order to at least be made aware of
the exception. This could be calling Wait/WaitAll on the Task. It
could be checking the Task’s Exception property after the Task has
completed. Or it could be using a Task’s Result property.
If a Task sees that its exception has been observed in some manner,
life is good. If, however, all references to a Task are removed
(making the Task available for garbage collection), and if its
exception hasn’t yet been observed, the Task knows that its exception
will never be observed. In such a case, the Task takes advantage of
finalization, and uses a helper object to propagate the unhandled
exception on the finalizer thread. With the behavior described
earlier, that exception on the finalizer thread will go unhandled and
invoke the default unhandled exception logic, which is to log the
issue and crash the process.
He also suggested two useful extension methods for handling exceptions in "fire-and-forget" tasks: one ignoring the exception and the other one immediately crashing the process:
public static Task IgnoreExceptions(this Task task)
{
task.ContinueWith(c => { var ignored = c.Exception; },
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.DetachedFromParent);
return task;
}
public static Task FailFastOnException(this Task task)
{
task.ContinueWith(c => Environment.FailFast(“Task faulted”, c.Exception),
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.DetachedFromParent);
return task;
}
In .NET 4.5 the default behavior has changed. Again, a quote from another Stephen Toub's post on the subject (thanks to mike z for bringing it to my attention in the comments):
To make it easier for developers to write asynchronous code based on
Tasks, .NET 4.5 changes the default exception behavior for unobserved
exceptions. While unobserved exceptions will still cause the
UnobservedTaskException event to be raised (not doing so would be a
breaking change), the process will not crash by default. Rather, the
exception will end up getting eaten after the event is raised,
regardless of whether an event handler observes the exception. This
behavior can be configured, though.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…