C# Контроль фоновых Task

В программе есть фоновая задача (ожидание запросов через HttpListener), внутри этой задачи при поступлении запроса создаю задачу которая обрабатывает сам контекст запроса. Задачи по обработке контекста запроса складываю в HashSet, запрос может обрабатываться долго (до 5 сек.) и в HashSet периодически копятся задачи. Хотел запустить фоновую задачу для контроля над HashSet, т.е. получать первую завершенную задачу, удалять ее из HashSet и логировать результат завершения задачи

StartListen() - запускает все задачи, но на момент старта запросы не поступали и HashSet пуста, поэтому ожидать через WhenAny первую завершенную задачу не получается. я могу в цикле вручную, например раз в 100мс проверять элементы HashSet и искать там завершенные задачи. Вот у меня и вопрос это делается в ручную или как то иначе можно?

    private readonly  HashSet<Task<Result>> _httpContextTasks = new HashSet<Task<Result>>();


    public Result<Task> StartListen()
    {
        _cts =  new CancellationTokenSource();         
        _bgTask = BackgroundController4ContextHandlers(_cts.Token);
        return ListenHttpAsync(_cts.Token);
    }


    private async Task BackgroundController4ContextHandlers(CancellationToken ct)
    {
        await Task.Run(async () =>
        {
            while (!ct.IsCancellationRequested)
            {
                var completedTask = await Task.WhenAny(_httpContextTasks);
                _httpContextTasks.Remove(completedTask);

                var res = completedTask.Result;
                var strResult = res.ToString();
                _logger.Information("{HttpServer}","ЗАПРОС ОБРАБОТАН", strResult);
            }
            _logger.Information("{HttpServer}","ФОНОВАЯ обработка Task запросов остановлена");
        }, ct);
    }
    
    
    private async Task ListenHttpAsync(CancellationToken ct)
    {
        _listener.Start();
        _logger.Information("{HttpServer}", "Ожидание запросов ...");
        while (!ct.IsCancellationRequested)
        {
            try
            {
                var context = await _listener.GetContextAsync();
                var handler = HttpListenerContextHandlerAsync(context, ct);
                _httpContextTasks.Add(handler);
            }
            catch (TaskCanceledException) { }
        }
    }

Ответы (1 шт):

Автор решения: aepot

Если просто в лоб оптимизировать код, то у меня бы что-то такое получилось

public Result<Task> StartListen()
{
    _cts =  new CancellationTokenSource();         
    return ListenHttpAsync(_cts.Token);
}
    
private async Task ListenHttpAsync(CancellationToken ct)
{
    _listener.Start();
    _logger.Information("{HttpServer}", "Ожидание запросов ...");
    try
    {
        while (true)
        {
            var context = await _listener.GetContextAsync();
            var handler = HttpListenerContextHandlerAsync(context, ct);
            handler.ContinueWith(t =>
            {
                if (t.IsCompletedSuccessfully)
                {
                    var strResult = t.Result.ToString();
                    _logger.Information("{HttpServer}","ЗАПРОС ОБРАБОТАН", strResult);
                }
                else
                {
                    var strError = t.Exception.Message;
                    _logger.Warning("{HttpServer}","ЗАПРОС НЕ ОБРАБОТАН", strError);
                }
            }, ct);
        }
    }
    catch (OperationCanceledException ex)
    {
        _logger.Information("{HttpServer}","Конец ожидания запросов", ex.Message);
    }
}

Единственный нюанс, я здесь вообще не ожидаю запущеную таску и нигде ее не храню. Поэтому внутри континуации обрабатываю исключение, но так же нужно учесть, чтобы внутри самой континуации все исключения были так же обработаны и она завершилась успешно. Такой подход прикольнее в плане производительности, нет коллекции, по которой надо постоянно бегать и искать, где же там таск завершенный остался. Таск просто запускает колбэк, когда успешно или не успешно завершается.

→ Ссылка