Как запретить 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
,