如何防止 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
.
我这里有问题。在下面的代码中,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
.