【问题标题】:Simple Task-returning Asynchronous HtppListener with async/await and handling high load具有异步/等待和处理高负载的简单任务返回异步 HtppListener
【发布时间】:2012-11-22 11:52:12
【问题描述】:

我创建了以下简单的HttpListener 来同时处理多个请求(在 .NET 4.5 上):

class Program {

    static void Main(string[] args) {

        HttpListener listener = new HttpListener();
        listener.Prefixes.Add("http://+:8088/");
        listener.Start();
        ProcessAsync(listener).ContinueWith(task => { });
        Console.ReadLine();
    }

    static async Task ProcessAsync(HttpListener listener) {

        HttpListenerContext ctx = await listener.GetContextAsync();

        // spin up another listener
        Task.Factory.StartNew(() => ProcessAsync(listener));

        // Simulate long running operation
        Thread.Sleep(1000);

        // Perform
        Perform(ctx);

        await ProcessAsync(listener);
    }

    static void Perform(HttpListenerContext ctx) {

        HttpListenerResponse response = ctx.Response;
        string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
        byte[] buffer = Encoding.UTF8.GetBytes(responseString);

        // Get a response stream and write the response to it.
        response.ContentLength64 = buffer.Length;
        Stream output = response.OutputStream;
        output.Write(buffer, 0, buffer.Length);

        // You must close the output stream.
        output.Close();
    }
}

我使用 Apache Benchmark Tool 对此进行负载测试。当我发出 1 个请求时,我得到一个请求的最大等待时间为 1 秒。例如,如果我发出 10 个请求,响应的最长等待时间会达到 2 秒。

您将如何更改我上面的代码以使其尽可能高效?

编辑

在@JonSkeet 的回答之后,我将代码更改如下。最初,我试图模拟一个阻塞调用,但我想这是核心问题。所以,我接受了@JonSkeet 的建议并将其更改为 Task.Delay(1000)。现在,下面的代码给出了最大值。等待时间约为。 10 个并发请求需要 1 秒:

class Program {

    static bool KeepGoing = true;
    static List<Task> OngoingTasks = new List<Task>();

    static void Main(string[] args) {

        HttpListener listener = new HttpListener();
        listener.Prefixes.Add("http://+:8088/");
        listener.Start();
        ProcessAsync(listener).ContinueWith(async task => {

            await Task.WhenAll(OngoingTasks.ToArray());
        });

        var cmd = Console.ReadLine();

        if (cmd.Equals("q", StringComparison.OrdinalIgnoreCase)) {
            KeepGoing = false;
        }

        Console.ReadLine();
    }

    static async Task ProcessAsync(HttpListener listener) {

        while (KeepGoing) {
            HttpListenerContext context = await listener.GetContextAsync();
            HandleRequestAsync(context);

            // TODO: figure out the best way add ongoing tasks to OngoingTasks.
        }
    }

    static async Task HandleRequestAsync(HttpListenerContext context) {

        // Do processing here, possibly affecting KeepGoing to make the 
        // server shut down.

        await Task.Delay(1000);
        Perform(context);
    }

    static void Perform(HttpListenerContext ctx) {

        HttpListenerResponse response = ctx.Response;
        string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
        byte[] buffer = Encoding.UTF8.GetBytes(responseString);

        // Get a response stream and write the response to it.
        response.ContentLength64 = buffer.Length;
        Stream output = response.OutputStream;
        output.Write(buffer, 0, buffer.Length);

        // You must close the output stream.
        output.Close();
    }
}

【问题讨论】:

  • 感谢您提供完整的解决方案。
  • @JonSkeet 需要对上述模式进行哪些修改才能使其服务于服务器发送事件,在这种情况下,响应流永远不会真正“关闭”。例如,我们如何使用它来“持续推送”数据到连接的客户端?
  • @CharlesO 我没有尝试过,但我假设如果你不在输出流上调用 output.Close(); 并且每次推送都刷新,它应该可以工作(当然,正确的标题上证所)。这也可能有所帮助:channel9.msdn.com/Events/TechDays/Techdays-2012-the-Netherlands/…

标签: c# .net http httplistener system.net


【解决方案1】:

在我看来,您最终会得到听众的分歧。在ProcessAsync 中,您启动一​​个新任务来监听(通过Task.Factory.StartNew),然后在方法结束时调用ProcessAsync再次。那怎么可能结束?目前尚不清楚这是否是您的性能问题的原因,但总体上看起来肯定是一个问题。

我建议将您的代码更改为一个简单的循环:

static async Task ProcessAsync(HttpListener listener) {
    while (KeepGoing) {
        var context = await listener.GetContextAsync();
        HandleRequestAsync(context);         
    }
}

static async Task HandleRequestAsync(HttpListenerContext context) {
    // Do processing here, possibly affecting KeepGoing to make the 
    // server shut down.
}

目前上面的代码忽略了HandleRequestAsync的返回值。您可能想要保留“当前运行中”任务的列表,当您被要求关闭时,请使用await Task.WhenAll(inFlightTasks) 以避免过快地关闭服务器。

还要注意Thread.Sleep 是一个阻塞 延迟。异步延迟为await Task.Delay(1000)

【讨论】:

  • 谢谢乔恩!我故意将阻塞延迟放在那里,以模拟阻塞操作,看看它将如何处理这些操作。
  • @tugberk:但是由于您目前编写代码的方式,阻塞操作可能有时最终会阻塞接受线程(我认为)。如果您尝试对繁重的 CPU 工作进行建模,则应该在单独的任务中执行该 CPU 工作并等待该任务。如果您尝试对 IO 建模,则应该异步等待。
  • 你是对的。核心问题是(我猜)我所做的阻塞“睡眠”。我编辑了我的问题并将新代码放在那里。它仍然错过了一些重要的步骤(我猜),但现在更好了,多亏了你。
  • 我正在寻找实现inFlightTasks 的示例。 List&lt;Task&gt; 不起作用,因为它不是线程安全的。我看过ConcurrentBag&lt;Task&gt;,但我不知道如何删除已完成的任务,因为该类上没有Remove 方法,TryTake 似乎不允许指定要使用的项目。
  • @CoderDennis:听起来你应该问一个更详细的新问题。
猜你喜欢
  • 2014-10-01
  • 2021-11-01
  • 2013-02-10
  • 1970-01-01
  • 2023-03-24
  • 2014-03-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多