Как запретить HttpListener прерывать отложенные запросы при остановке? HttpListener.Stop не работает

У меня есть проблема здесь. В приведенном ниже коде шаблон async/await используется с HttpListener. Когда запрос отправляется через HTTP, ожидается наличие строки запроса HTTP "delay" и его значение заставляет сервер задерживать указанную обработку запроса на данный период. Мне нужен сервер для обработки ожидающих запросов даже после того, как сервер перестал получать новые запросы.

static void Main(string[] args)
{
    HttpListener httpListener = new HttpListener();

    CountdownEvent sessions = new CountdownEvent(1);
    bool stopRequested = false;

    httpListener.Prefixes.Add("http://+:9000/GetData/");

    httpListener.Start();

    Task listenerTask = Task.Run(async () =>
    {
        while (true)
        {
            try
            {
                var context = await httpListener.GetContextAsync();

                sessions.AddCount();

                Task childTask = Task.Run(async () =>
                {
                    try
                    {
                        Console.WriteLine($"Request accepted: {context.Request.RawUrl}");

                        int delay = int.Parse(context.Request.QueryString["delay"]);

                        await Task.Delay(delay);

                        using (StreamWriter sw = new StreamWriter(context.Response.OutputStream, Encoding.Default, 4096, true))
                        {
                            await sw.WriteAsync("<html><body><h1>Hello world</h1></body></html>");
                        }

                        context.Response.Close();
                    }
                    finally
                    {
                        sessions.Signal();
                    }
                });
            }
            catch (HttpListenerException ex)
            {
                if (stopRequested && ex.ErrorCode == 995)
                {
                    break;
                }

                throw;
            }
        }
    });

    Console.WriteLine("Server is running. ENTER to stop...");

    Console.ReadLine();

    sessions.Signal();

    stopRequested = true;

    httpListener.Stop();

    Console.WriteLine("Stopped accepting requests. Waiting for the pendings...");

    listenerTask.Wait();

    sessions.Wait();

    Console.WriteLine("Finished");

    Console.ReadLine();

    httpListener.Close();
}

Точная проблема здесь в том, что когда сервер останавливается, вызывается HttpListener.Stop, но все ожидающие запросы немедленно прерываются, т. Е. Код не может отправить ответы обратно.

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

Подскажите, пожалуйста, что я делаю не так и как я могу запретить HttpListener прерывать отложенные запросы в шаблоне async/await?

1 ответ

Решение

Кажется, что когда HttpListener закрывает дескриптор очереди запросов, выполняемые запросы отменяются. Насколько я могу сказать, нет никакого способа избежать HttpListener сделать это - по-видимому, это вещь совместимости. В любом случае, вот как это GetContextсистема работает - когда ручка закрыта, нативный метод GetContext вызов, чтобы фактически получить контекст запроса, немедленно возвращает ошибку.

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

var context = await httpListener.GetContextAsync();

if (stopRequested) return;

... и так как нет лучшего API отмены для HttpListener, это действительно ваш единственный вариант, если вы хотите придерживаться HttpListener,

Выключение будет выглядеть так:

stopRequested = true;
sessions.Wait();

httpListener.Dispose();
listenerTask.Wait();

Я бы также предложил использовать CancellationToken вместо bool флаг - он обрабатывает все проблемы синхронизации для вас. Если по какой-то причине это нежелательно, убедитесь, что вы синхронизируете доступ к флагу - в соответствии с контрактом компилятору разрешено пропускать проверку, поскольку флаг не может быть изменен в однопоточном коде.

Если вы хотите, вы можете сделать listenerTask завершите раньше, отправив фиктивный HTTP-запрос себе сразу после установки stopRequested - это вызовет GetContext немедленно вернуться с новым запросом, и вы можете вернуться. Это подход, который обычно используется при работе с API, которые не поддерживают "хорошее" аннулирование, например UdpClient.Receive,

Другие вопросы по тегам