.NET and me Coding dreams since 1998!

21Sep/120

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

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.