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

Posted

技术标签:

【中文标题】如何防止 HttpListener 在停止时中止挂起的请求? HttpListener.Stop 不起作用【英文标题】:How to prevent HttpListener from aborting pending requests on stoppage? HttpListener.Stop is not working 【发布时间】:2016-04-19 08:01:08 【问题描述】:

我这里有问题。在下面的代码中,异步/等待模式与 HttpListener 一起使用。当请求通过 HTTP 发送时,预计会出现“延迟”查询字符串参数,其值会导致服务器将上述请求处理延迟给定时间段。即使在服务器停止接收新请求后,我也需要服务器处理待处理的请求。

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 以异步/等待模式中止挂起的请求?

【问题讨论】:

我的意思是我希望我的服务器停止接收新请求并能够处理待处理的请求。但是调用 HttpListener.Stop 会阻止我响应待处理的请求。至于异步/等待场景中的 CancellationToken,如果 HttpListener.GetContextAsync 根本不接受 CancellationToken,这对我有什么帮助? @Luaan,顺便谢谢你,我已经更新了问题的文字和标题。 @Luaan,似乎是一个错误。无法强制 HttpListener 暂停接收请求并处理待处理的请求。甚至尝试过 Begin/EndGetContext 对。仍然没有运气。实际上 Thread.Abort 方法有帮助,因为它能够在不停止请求处理逻辑的情况下中止对 HttpListener.GetContext 的调用。 @Luaan,再次感谢。我明白你的意思。事实证明,使用 Thread.Abort 的代码隐藏了 HttpListener.GetContext 挂起的点,并且在我调用 Thread.Stop 后它立即唤醒。 将 cmets 移至答案 :) 【参考方案1】:

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

Thread.Abort 没有帮助 - 实际上,我还没有看到在“应用程序域卸载”场景之外正确使用 Thread.Abort 的地方。 Thread.Abort 只能中止托管代码。由于您的代码当前正在本地运行,因此只有在返回托管代码时才会中止 - 这几乎完全等同于这样做:

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 立即返回新请求,并且您可以返回。这是一种在处理不支持“漂亮”取消的 API 时常用的方法,例如UdpClient.Receive.

【讨论】:

我选择了稍微不同的策略。我正在取消取消令牌,然后等待挂起的请求,而实际上并没有停止接收新的请求。如果在取消后有新请求到达,我只是将其中止,对于没有其他请求到达且 GetContextAsync 挂起的情况,我最终会在 HTTP 侦听器上调用 Stop 和 Close。

以上是关于如何防止 HttpListener 在停止时中止挂起的请求? HttpListener.Stop 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

如何在用户输入时中断 ocaml 程序

AHK 释放时中断循环并继续按键

如何在 C# 中的随机端口上创建 HttpListener 类?

如何在 iOS 中停止 JSContext 评估?

跳转到应用程序时中断不起作用(STM32)

使用 SSL 的 Android HttpListener?