如何防止 HttpListener 在停止时中止挂起的请求? HttpListener.Stop 不工作

How to prevent HttpListener from aborting pending requests on stoppage? HttpListener.Stop is not working

我这里有问题。在下面的代码中,async/await 模式与 HttpListener 一起使用。当通过 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,但所有待处理的请求都会立即中止,即代码无法发回响应。

在非async/await模式(即简单的基于线程的实现)中,我可以选择中止线程(我认为这是非常糟糕的),这将允许我处理挂起的请求,因为这只是中止 HttpListener.GetContext 调用。

能否请您指出,我做错了什么以及如何防止 HttpListener 以 async/await 模式中止未决请求?

似乎当 HttpListener 关闭请求队列句柄时,正在进行的请求被中止。据我所知,没有办法避免 HttpListener 那样做——显然,这是一个兼容性问题。无论如何,这就是它的 GetContext 结束系统的工作方式 - 当句柄关闭时,本机方法 GetContext 调用以实际获取请求上下文 return 立即出错。

Thread.Abort 没有帮助——真的,我还没有看到在 "application domain unloading" 场景之外正确使用 Thread.Abort 的地方。 Thread.Abort 只能中止 managed 代码。由于您的代码当前是 运行 本机代码,因此只有当它 return 返回托管代码时才会中止 - 这几乎完全等同于这样做:

var context = await httpListener.GetContextAsync();

if (stopRequested) return;

...并且由于 HttpListener 没有更好的取消 API,如果您想坚持使用 HttpListener,这确实是您唯一的选择。

关闭将如下所示:

stopRequested = true;
sessions.Wait();

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

我还建议使用 CancellationToken 而不是 bool 标志 - 它可以为您处理所有同步问题。如果出于某种原因不希望这样做,请确保同步对标志的访问 - 根据合同,允许编译器省略检查,因为标志不可能在单线程代码中更改。

如果你愿意,你可以通过在设置 stopRequested 后立即向自己发送一个虚拟 HTTP 请求来使 listenerTask 更快地完成 - 这将导致 GetContext 到 return 立即与新的请求,你可以 return。这是处理不支持 "nice" 取消的 API 时常用的方法,例如UdpClient.Receive.