mikhailshilkov / mikhailio-hugo

Sources of https://mikhail.io migrated to Hugo
MIT License
12 stars 8 forks source link

Comments to "How to Drain a List of .NET Tasks to Completion" #43

Open mikhailshilkov opened 3 years ago

mikhailshilkov commented 3 years ago

Add your comment to How to Drain a List of .NET Tasks to Completion.

The comments will be displayed directly on the page. I may edit or merge your comments in a non-significant way to make sure they are displayed properly on the website.

MortenHoustonLudvigsen commented 2 years ago

Thank you for this article. I think however your task list will never complete if it has no tasks.

Inspired by your article, I have made my own implementation:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
namespace MyNamespace
{
    public class TaskList
    {
        private HashSet<Guid> _tasks;
        private TaskCompletionSource<bool> _firstTask;
        private TaskCompletionSource<bool> _tcs;

        public TaskList()
        {
            Reset();
        }

        public async Task Run()
        {
            _firstTask.TrySetResult(true);
            await _tcs.Task.ConfigureAwait(false);
            Reset();
        }

        private void Reset()
        {
            _tasks = new HashSet<Guid>();
            _firstTask = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
            _tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
            Add(_firstTask.Task);
        }

        public void Add(params Task[] tasks)
            => Add(tasks.AsEnumerable());

        public void Add(IEnumerable<Task> tasks)
        {
            foreach (var task in tasks)
            {
                Add(task);
            }
        }

        public void Add(Task task)
        {
            var id = Guid.NewGuid();
            lock (_tasks)
            {
                _tasks.Add(id);
            }
            HandleCompletion(id, task);
        }

        private async Task HandleCompletion(Guid id, Task task)
        {
            try
            {
                await task.ConfigureAwait(false);
                await HandleSuccess(id, task).ConfigureAwait(false);
            }
            catch (OperationCanceledException ex)
            {
                await HandleCancellation(id, task, ex).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                await HandleError(id, task, ex).ConfigureAwait(false);
            }
            finally
            {
                lock (_tasks)
                {
                    _tasks.Remove(id);
                    if (_tasks.Count == 0)
                    {
                        _tcs.TrySetResult(true);
                    }
                }
            }
        }

        protected virtual async Task HandleSuccess(Guid id, Task task)
        {

        }

        protected virtual async Task HandleCancellation(Guid id, Task task, OperationCanceledException ex)
        {
        }

        protected virtual async Task HandleError(Guid id, Task task, Exception ex)
        {
        }
    }
}

Things to note: