Reputation: 6621
How do I determine if a task has already been awaited / had its result inspected?
I have a List
of background Task
s which are created and activated, but may not have been awaited due to exceptional behavior. In my 'finally' block, I need to iterate over this list and await
those tasks which have not already been awaited, as to prevent their potential exceptions being raised when they're GC'd.
EDIT
My solution:
Firstly, thanks to Steve for clearing up my outdated understanding of how unobserved exceptions are handled in .NET 4.5. This is very interesting. However, I am still interested in raising these exceptions from the invoking method, so I have come up with the following simple helper class to provide easy management of Task
s which aren't immediately observed:
/// <summary>
/// Collection of unobserved tasks.
/// </summary>
public class TaskCollection
{
/// <summary>
/// The tasks.
/// </summary>
private readonly List<Task> tasks = new List<Task>();
/// <summary>
/// Gets a value indicating whether this instance is empty.
/// </summary>
/// <value>
/// <c>true</c> if this instance is empty; otherwise, <c>false</c>.
/// </value>
public bool IsEmpty
{
get
{
return this.tasks.Count == 0;
}
}
/// <summary>
/// Adds the specified task.
/// </summary>
/// <param name="task">The task.</param>
/// <returns>The <see cref="task"/>.</returns>
public Task Add(Task task)
{
Contract.Requires(task != null);
Contract.Requires(!this.Contains(task));
Contract.Ensures(this.Contains(task));
Contract.Ensures(Contract.Result<Task>() == task);
this.tasks.Add(task);
return task;
}
/// <summary>
/// Adds the specified task.
/// </summary>
/// <typeparam name="T">Task return type.</typeparam>
/// <param name="task">The task.</param>
/// <returns>The <see cref="task"/>.</returns>
public Task<T> Add<T>(Task<T> task)
{
Contract.Requires(task != null);
Contract.Requires(!this.Contains(task));
Contract.Ensures(this.Contains(task));
Contract.Ensures(Contract.Result<Task<T>>() == task);
this.tasks.Add(task);
return task;
}
/// <summary>
/// Determines whether [contains] [the specified task].
/// </summary>
/// <param name="task">The task.</param>
/// <returns>
/// <c>true</c> if [contains] [the specified task]; otherwise, <c>false</c>.
/// </returns>
public bool Contains(Task task)
{
Contract.Requires(task != null);
return this.tasks.Contains(task);
}
/// <summary>
/// Observes the specified task.
/// </summary>
/// <param name="task">The task.</param>
/// <returns>
/// The task.
/// </returns>
public Task When(Task task)
{
Contract.Requires(task != null);
Contract.Requires(this.Contains(task));
Contract.Ensures(!this.Contains(task));
this.tasks.Remove(task);
return task;
}
/// <summary>
/// Observes the specified task.
/// </summary>
/// <typeparam name="T">Return type.</typeparam>
/// <param name="task">The task.</param>
/// <returns>
/// The task.
/// </returns>
public Task<T> When<T>(Task<T> task)
{
Contract.Requires(task != null);
Contract.Requires(this.Contains(task));
Contract.Ensures(!this.Contains(task));
this.tasks.Remove(task);
return task;
}
/// <summary>
/// Observes all tasks.
/// </summary>
/// <returns>The task.</returns>
public Task WhenAll()
{
Contract.Ensures(this.IsEmpty);
if (this.IsEmpty)
{
return TaskEx.FromResult<object>(null);
}
var taskArray = this.tasks.ToArray();
this.tasks.Clear();
return TaskEx.WhenAll(taskArray);
}
}
Example usage:
Exception exception;
var tasks = new TaskCollection();
try
{
var backgroundTask = tasks.Add(DoSomethingAsync());
// Do something in parallel.
await tasks.When(backgroundTask);
}
catch (Exception ex)
{
if (tasks.IsEmpty)
{
throw;
}
exception = ex;
}
try
{
await tasks.WhenAll();
}
catch (Exception ex)
{
exception = new AggregateException(ex, exception);
}
throw new AggregateException(exception);
Upvotes: 1
Views: 534
Reputation: 457472
In my 'finally' block, I need to iterate over this list and await those tasks which have not already been awaited, as to prevent their potential exceptions being raised when they're GC'd.
No, you don't.
In .NET 4.5, unobserved task exceptions are no longer raised during finalization.
Upvotes: 5
Reputation: 1064304
You need to be careful here... once things start getting ill, it becomes hard to guarantee that that task is ever going to complete (any which way). But - you could add a method like:
public static void IgnoreFailure(this Task task) {
if (task.IsCompleted) { // observe now
// this is just an opaque method to ensure it gets evaluated
if (task.IsFaulted) GC.KeepAlive(task.Exception);
}
else { // observe in the future
task.ContinueWith(t => {
if (t.IsFaulted) GC.KeepAlive(t.Exception);
}, TaskContinuationOptions.ExecuteSynchronously);
}
}
and just call t.IgnoreFailure()
on each task, whether it has been awaited or not.
Upvotes: 2