Awaitable task progress reporting
In my previous Task.WhileAll post I had a very fishy piece of code in sample where I put a Thread to sleep in order to give enough time for async composition to complete so the test could pass.
Well, every time I use a Thread.Sleep anywhere I know it is a bad thing so I've decided to get rid of it.
IProgressAsync<T>
The problem is related to the fact that IProgress<T> interface defines a void handler which can't be awaited and thus it ruins composition.
That's why I decided to define my own "true async" version of the interface which looks has the same report method but returning a Task I can await.
namespace WhileAllTests
{
using System.Threading.Tasks;
public interface IProgressAsync<in T>
{
Task ReportAsync(T value);
}
}
Having a async version of reporting is VERY useful when the subscriber itself is awaiting further calls. I could get that with async void but using async void IMHO always turns to be bad solution so I choose to use the Task returning signature even I need a custom made interface for that.
And here's the implementation
using System;
using System.Threading.Tasks;
public class ProgressAsync<T> : IProgressAsync<T>
{
private readonly Func<T, Task> handler;
public ProgressAsync(Func<T, Task> handler)
{
this.handler = handler;
}
public async Task ReportAsync(T value)
{
await this.handler.Invoke(value);
}
}
No magic here:
- Instead of Action<T> my ctor accepts the Func<T, Task> so I can await it
- ReportAsync awaits in async manner the provided Task enabling composition
Now having this in place I can update my task extension method to compose reporting method invocation
public static async Task<IList> WhileAll(this IList<Task> tasks, IProgressAsync progress)
{
var result = new List(tasks.Count);
var remainingTasks = new List<Task>(tasks);
while (remainingTasks.Count > 0)
{
await Task.WhenAny(tasks);
var stillRemainingTasks = new List<Task>(remainingTasks.Count - 1);
for (int i = 0; i < remainingTasks.Count; i++)
{
if (remainingTasks[i].IsCompleted)
{
result.Add(remainingTasks[i].Result);
await progress.ReportAsync(remainingTasks[i].Result);
}
else
{
stillRemainingTasks.Add(remainingTasks[i]);
}
}
remainingTasks = stillRemainingTasks;
}
return result;
}
With all this in place I can remove thread sleep from my unit tests and have it more useful
[TestClass]
public class UnitTest1 {
Listresult = new List();
[TestMethod]
[TestClass]
public class UnitTest1 {
List<int> result = new List<int>();
[TestMethod]
public async Task TestMethod1() {
var task1 = Task.Run(() => 101);
var task2 = Task.Run(() => 102);
var tasks = new List<Task<int>>() { task1, task2 };
var listener = new ProgressAsync<int>(this.OnProgressAsync);
var actual = await tasks.WhileAll(listener);
Assert.AreEqual(2, this.result.Count);
Assert.IsTrue(this.result.Contains(101));
Assert.IsTrue(this.result.Contains(102));
Assert.AreEqual(2, actual.Count);
Assert.IsTrue(actual.Contains(101));
Assert.IsTrue(actual.Contains(102));
}
private async Task OnProgressAsync(int arg) { result.Add(arg); }
}
There you go, updated code of Task.WhileAll can be found here