【问题标题】:C# HttpListener handling requests with multiple threadsC# HttpListener 处理多线程请求
【发布时间】:2021-04-09 09:31:57
【问题描述】:

我必须申请2个方法:

  1. 使用不同的线程/任务处理每个 HTTP 请求。
  2. 在调用取消令牌时优雅地完成请求。

现在,我遇到了一个问题,即当调用取消令牌并且请求未到达时 - 我堆叠在“server.getContext”阻塞区域中。 知道如何解决吗?

public void Listen()
{
  CancellationTokenSource source = new CancellationTokenSource();
  CancellationToken token = source.Token;
  server.Start();
  Console.WriteLine($"Waiting for connections on {url}");
  HandleShutdownWhenKeyPressed(source);
  HandleIncomingHTTPRequests(token);
  server.Close();
}

void HandleIncomingHTTPRequests(CancellationToken token)
{
  while (!token.IsCancellationRequested)
  {
    HttpListenerContext ctx = server.GetContext();
    // I stack here until the request has arrived even if the cancellation token has invoked.
    Task.Run(() =>
    {
      HttpListenerRequest req = ctx.Request;
      HttpListenerResponse res = ctx.Response;
      // SOME STUFF ....
    },token)
  }
 }

提前致谢。

【问题讨论】:

  • "用不同的线程/任务处理每个 HTTP 请求。" - 这实际上是两种不同的方法:使用 Task 意味着 使用不同的线程,因为您将使用异步 IO 和线程池。
  • 你的HandleIncomingHTTPRequests方法应该被标记为async Task 不是 void - 你不需要使用Task.Run
  • 嗨@Dai,为什么不呢?如果我想用不同的任务(比如线程池中的不同线程)执行请求 - 否则我该如何实现?
  • 使用HttpListener.GetContextAsync - 它为你处理异步IO。
  • @Dai - 据我所知 - 与线程池不同的线程!= 异步 IO 操作。我在写吗?

标签: c# multithreading httplistener


【解决方案1】:

您不需要Task.Run,而async IO 的美妙之处在于您不需要自己处理ThreadThreadPoolTask.RunIO 完成已经被处理为您通过实际实现(在本例中为HttpListener,但在其他情况下可能为FileStreamSocket 等)。

你只需要这个:

public static async Task RunHttpListenerAsync( HttpListener listener, CancellationToken ct )
{
    listener.Start();

    while( !ct.IsCancellationRequested )
    {
        HttpListenerContext ctx = await listener.GetContextAsync().ConfigureAwait(false);
        
        try
        {
            await ProcessRequestAsync( ctx ).ConfigureAwait(false);
        }
        catch( Exception ex )
        {
            // TODO: Log `ex`.
        }
    }
}

private static async Task ProcessRequestAsync( HttpListenerContext ctx )
{
    HttpListenerRequest  req = ctx.Request;
    HttpListenerResponse res = ctx.Response;

    // do stuff
}

如果您的程序在没有需要在单个线程上恢复的同步上下文的情况下运行(即您没有在 WinForms 或 WPF 中运行)-或-您在任何地方都使用.ConfigureAwait(false) (like you should ) 然后每次你的程序屈服于调度程序时,它都会在它想要的线程池中的任何后台线程上恢复,包括可以同时恢复当前Task 的任何可用线程。

【讨论】:

  • 这是不正确的。 OP 的代码能够并行处理多个请求。你的总是一次只处理一个。虽然使用await listener.GetContextAsync() 很好,因为它可以防止一个线程等待它,但您仍然需要Task.Run 来处理请求
  • @KevinGosse “你的总是一次只处理一个”——这过于简单化了。是的:我发布的代码将以 serial 方式处理新的传入请求,但是一旦请求进入ProcessRequestAsync,这些任务可能会同时恢复 - 并且@987654336 的序列化@ 很少成为 Web 应用程序的瓶颈。
  • 好吧。您将同时处理请求的代码更改为可能同时处理它们的代码,假设ProcessRequestAsync有任何异步部分,OP从未提及
  • @KevinGosse “您更改了同时处理请求的代码” - 如果程序在只有一个硬件线程的机器内运行,这将是次优的。同样,我的回答中的“可能”是指在给定硬件(或 VM)约束的情况下,可以信任 CLR 中的任务调度程序以最佳线程数运行。
  • 我们很清楚,你的代码大部分是正确的,但你应该写_ = Task.Run(() => ProcessRequests(ctx);而不是await ProcessRequestAsync( ctx )...
猜你喜欢
  • 2012-02-20
  • 2014-12-17
  • 2013-10-18
  • 2023-04-01
  • 2012-10-08
  • 2014-11-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多