Хорошо, это может быть немного затянуто, но вот оно.
Основная проблема здесь в том, что HttpListener.GetContextAsync()
не поддерживает отмену через CancellationToken
. Так что сложно отменить эту операцию каким-то изящным образом. Что нам нужно сделать, так это «подделать» отмену.
Стивен Туб является мастером в шаблоне async
/ await
. К счастью для нас, он написал статью под названием Как мне отменить неотменяемые операции asyn c? . Вы можете проверить это здесь .
Я не верю в использование события AppDomain.CurrentDomain.ProcessExit
. Вы можете прочитать, почему некоторые пытаются этого избежать.
Я буду использовать событие Console.CancelKeyPress .
Итак, в программном файле у меня есть настройте его так:
Program.cs
class Program
{
private static readonly CancellationTokenSource _cancellationToken =
new CancellationTokenSource();
static async Task Main(string[] args)
{
var http = new SimpleHttpServer();
var taskRunHttpServer = http.RunAsync(_cancellationToken.Token);
Console.WriteLine("Now after http.RunAsync();");
Console.CancelKeyPress += (s, e) =>
{
_cancellationToken.Cancel();
};
await taskRunHttpServer;
Console.WriteLine("Program end");
}
}
Я взял ваш код и добавил событие Console.CancelKeyPress
и добавил CancellationTokenSource
. Я также изменил ваш метод SimpleHttpServer.RunAsync()
, чтобы принять токен из этого источника:
SimpleHttpServer.cs
public class SimpleHttpServer
{
private readonly HttpListener _httpListener;
public SimpleHttpServer()
{
_httpListener = new HttpListener();
_httpListener.Prefixes.Add("http://127.0.0.1:5100/");
}
public async Task RunAsync(CancellationToken token)
{
try
{
_httpListener.Start();
while (!token.IsCancellationRequested)
{
// ...
var context = await _httpListener.GetContextAsync().
WithCancellation(token);
var response = context.Response;
// ...
}
}
catch(OperationCanceledException)
{
// we are going to ignore this and exit gracefully
}
}
}
Вместо того, чтобы зацикливаться на true
, я теперь l oop от того, сигнализируется ли токен об отмене или нет.
Еще одна довольно странная вещь - это добавление метода WithCancellation
к строке _httpListener.GetContextAsync()
.
Этот код взят из статьи Стивена Туба выше. Я создал новый файл, который предназначен для хранения расширений для задач:
TaskExtensions.cs
public static class TaskExtensions
{
public static async Task<T> WithCancellation<T>(
this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
return await task;
}
}
Я не буду go подробно рассказывать как это работает, потому что в статье выше это прекрасно объясняется.
Теперь, когда вы поймаете сигнал CTRL + C, токен получит сигнал об отмене, который выдаст OperationCanceledException
, который разбивает этот l oop . Мы ловим его, отбрасываем в сторону и выходим.
Если вы хотите продолжать использовать AppDomain.CurrentDomain.ProcessExit
, вы можете - на ваш выбор ... просто добавьте код внутри Console.CancelKeyPress
в это событие.
Программа завершится корректно ... ну, максимально изящно.