Lawrence Wagerfield
Lawrence Wagerfield

Reputation: 6621

Determine if a Task has been awaited

How do I determine if a task has already been awaited / had its result inspected?

I have a List of background Tasks 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 Tasks 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

Answers (2)

Stephen Cleary
Stephen Cleary

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

Marc Gravell
Marc Gravell

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

Related Questions