I use a set of tasks at times, and in order to make sure they are all awaited I use this approach:
public async Task ReleaseAsync(params Task[] TaskArray)
{
From the comment:
these [tasks] were tied to managed resources and I wanted to release them when they became available instead of waiting for all of them to complete and then releasing.
Using a helper async void method may give you the desired behavior for both removing the finished tasks from the list and immediately throwing unobserved exceptions:
public static class TaskExt
{
public static async void Observe(Task task)
{
await task;
}
public static async Task WithObservation(Task task)
{
try
{
return await task;
}
catch (Exception ex)
{
// Handle ex
// ...
// Or, observe and re-throw
task.Observe(); // do this if you want to throw immediately
throw;
}
}
}
Then your code might look like this (untested):
async void Main()
{
Task[] TaskArray = new Task[] { run().WithObservation() };
var tasks = new HashSet(TaskArray);
while (tasks.Any()) tasks.Remove(await Task.WhenAny(tasks));
}
.Observe() will re-throw the task's exception immediately "out-of-band", using SynchronizationContext.Post if the calling thread has a synchronization context, or using ThreadPool.QueueUserWorkItem otherwise. You can handle such "out-of-band" exceptions with AppDomain.CurrentDomain.UnhandledException).
I described this in more details here:
TAP global exception handler