1

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

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

    private readonly  HashSet<Task<Result>> _httpContextTasks = new HashSet<Task<Result>>();
public Result&lt;Task&gt; StartListen()
{
    _cts =  new CancellationTokenSource();         
    _bgTask = BackgroundController4ContextHandlers(_cts.Token);
    return ListenHttpAsync(_cts.Token);
}


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

            var res = completedTask.Result;
            var strResult = res.ToString();
            _logger.Information(&quot;{HttpServer}&quot;,&quot;ЗАПРОС ОБРАБОТАН&quot;, strResult);
        }
        _logger.Information(&quot;{HttpServer}&quot;,&quot;ФОНОВАЯ обработка Task запросов остановлена&quot;);
    }, ct);
}


private async Task ListenHttpAsync(CancellationToken ct)
{
    _listener.Start();
    _logger.Information(&quot;{HttpServer}&quot;, &quot;Ожидание запросов ...&quot;);
    while (!ct.IsCancellationRequested)
    {
        try
        {
            var context = await _listener.GetContextAsync();
            var handler = HttpListenerContextHandlerAsync(context, ct);
            _httpContextTasks.Add(handler);
        }
        catch (TaskCanceledException) { }
    }
}

Aldmi
  • 1,925
  • 3
    HashSet не является потокобезопасным. Нужно использовать какую-либо потокобезопасную коллекцию или синхронизацию. А вообще, по-моему, у вас классический producer-consumer. Вот ещё: 1, 2 – Alexander Petrov Dec 01 '20 at 11:00
  • 1
    В дополнение к выше сказанному: await Task.Run(async () => - этот враппер вообще не нужен, он просто создает лишний поток, у вас и так все асинхронно работает. – aepot Dec 01 '20 at 14:53
  • Насчет Task.Run согласен, producer-consumer почитаю. Спасибо – Aldmi Dec 01 '20 at 14:59
  • 1
    Stephen Toub - Processing tasks as they complete. Велосипед уже разжеван. Если интересуют новые инструменты для решения этой задачи - то почитайте про System.Threading.Channels, а если постарше, то ConcurrentBag может помочь для WhenAny, но я бы вообще не использовал WhenAny, просто континуации, или их подобие с колбэками. В этом случае вообще коллекция не нужна. – aepot Dec 01 '20 at 15:03
  • Task<Result>, Result<Task> - круто :) Назовите класс поинтереснее это же что-то типа IActionResult? – aepot Dec 01 '20 at 15:16
  • Result - метод возвращает результат, задача может не запустится, т.е. команда не выполнится, это отражено в Result. Task - в коллекции задач обработки запроса, да пока результат просто True/False и строка ошибки если false. – Aldmi Dec 01 '20 at 15:23
  • Куда помещать фоновые задачи и как с ними работать давно вопрос назревал, спасибо за ссылки – Aldmi Dec 01 '20 at 15:25

1 Answers1

0

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

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); } }

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

aepot
  • 49,560
  • 1
    через ContinueWith в том же потоке, да самое простое пожалуй решение. Попробовал помещать эти задачи в BlockingCollection<Task>(), вроде работает, постараюсь этот вариант развить, чтобы что-то новое освоить. СПАСИБО за помощь!!! – Aldmi Dec 01 '20 at 15:44