更新时间:2021-08-27 00:32:34
好吧,这可能有点啰嗦,但就是这样.
Ok, this may be a tad long winded, but here it goes.
这里的主要问题是 HttpListener.GetContextAsync()
不支持通过 CancellationToken
取消.所以很难以一种优雅的方式取消这个操作.我们需要做的是造假"取消.
The main issue here is HttpListener.GetContextAsync()
does not support cancellation via CancellationToken
. So it's tough to cancel this operation in a somewhat graceful manner. What we need to do is "fake" a cancellation.
Stephen Toub 是 async
/await
模式的大师.幸运的是,他写了一篇题为如何取消不可取消的异步操作?的文章.您可以在此处查看.
Stephen Toub is a master in the async
/await
pattern. Luckily for us he wrote an article entitled How do I cancel non-cancelable async operations?. You can check it out here.
我不相信使用 AppDomain.CurrentDomain.ProcessExit
事件.你可以阅读为什么有些人试图避免它.
I don't believe in using the AppDomain.CurrentDomain.ProcessExit
event. You can read up on why some folks try to avoid it.
我将使用 控制台.CancelKeyPress 事件.
所以,在程序文件中,我是这样设置的:
So, in the program file, I have set it up like this:
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()
方法以接受来自该来源的令牌:
I took your code and added the Console.CancelKeyPress
event and added a CancellationTokenSource
. I also modified your SimpleHttpServer.RunAsync()
method to accept a token from that source:
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
,而是循环判断令牌是否已取消.
Instead of looping on true
, I now loop on the whether or not the token is signaled as cancelled or not.
另一件很奇怪的事情是在 _httpListener.GetContextAsync()
行中添加了 WithCancellation
方法.
Another thing that is quite odd about this is the addition of WithCancellation
method to the _httpListener.GetContextAsync()
line.
此代码来自上面的 Stephen Toub 文章.我创建了一个用于保存任务扩展名的新文件:
This code is from the Stephen Toub article above. I created a new file that is meant to hold extensions for tasks:
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;
}
}
我不会详细介绍它的工作原理,因为上面的文章解释得很好.
I won't go in to much detail about how it works because the article above explains it just fine.
现在,当您捕捉到 CTRL+C 信号时,令牌会发出取消信号,这将抛出一个 OperationCanceledException
中断该循环.我们接住它,把它扔到一边然后出去.
Now, when you catch the CTRL+C signal, the token is signaled to cancel which will throw a OperationCanceledException
which breaks that loop. We catch it and toss it aside and exit.
如果您想继续使用 AppDomain.CurrentDomain.ProcessExit
,您可以——您的选择.. 只需将 Console.CancelKeyPress
中的代码添加到其中活动.
If you want to continue to use AppDomain.CurrentDomain.ProcessExit
, you can -- your choice.. just add the code inside of Console.CancelKeyPress
in to that event.
然后程序将优雅地退出......好吧,尽可能优雅地退出.
The program will then exit gracefully... well, as gracefully as it can.