Transforming a task sequence into a new collection in completion order

Example from book “c# in depth 3rd edition, Jon Skeet (page 508)” is to allow you to pass in a sequence of input tasks, and the method will return a sequence of output tasks. The results of the tasks in the two sequences will be the same, but with one crucial difference: the output tasks will complete in the order they’re provided, so you can await them one at a time and know you’ll get the results as soon as they’re available.

 public static class ExtensionClass
    {
        public static IEnumerable<Task<T>> InCompletionOrder<T>(this IEnumerable<Task<T>> source)
        {
            var inputs = source.ToList();
            var boxes = inputs.Select(x => new TaskCompletionSource<T>()).ToList();
            var results = new Task<Task<T>>[inputs.Count];

            int currentIndex = -1;
            foreach (var task in inputs)
            {
                task.ContinueWith(completed =>
                {
                    var nextBox = boxes[Interlocked.Increment(ref currentIndex)];
                    if (completed.IsFaulted)
                        nextBox.TrySetException(completed.Exception.InnerExceptions);
                    else
                        if (completed.IsCanceled)
                        nextBox.TrySetCanceled();
                    else
                    {
                        nextBox.TrySetResult(completed.Result);
                        
                    }
                }, TaskContinuationOptions.ExecuteSynchronously);
            }
            return boxes.Select(box => box.Task);
        }
    }

Work with this extension method

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string[] urls = { "http://www.ya.ru", "http://www.mail.ru", "http://www.yandex.ru" };
            ShowPageLengthAsync(listBox1, urls);
        }

        private static async Task<int> ShowPageLengthAsync(ListBox lb, params string[] urls)
        {
            var tasks = urls.Select(async url =>
            {
                using (var client = new HttpClient()) 
                {
                    return await client.GetStringAsync(url);
                }
            }).ToList();
            int total = 0;
            foreach (var task in tasks.InCompletionOrder())
            {
                var page = await task;
                lb.Items.Add(String.Format("Got page length {0}", page.Length));
                total += page.Length;
            }
            return total;
        }

 

 

Categories: C#

Leave a Comment