1

I use standard HttpListener from System.Net namespace. When I use Task.Delay to simulate some work on server side (without await) and test server with Apache benchmark it gives good results (2000 rps). But when I'm "awaiting" bandwith is about 9 rps (according to Apache Benchmark). Why is it behave like this? Appreciate for answers.

private async Task Listen()
{
    while (listener.IsListening)
    {
        try
        {
            var context = await listener.GetContextAsync().ConfigureAwait(false);

            context.Response.StatusCode = (int)HttpStatusCode.OK;
            // good without await
            await Task.Delay(100).ConfigureAwait(false);

            using (var sw = new StreamWriter(context.Response.OutputStream))
            {
                await sw.WriteAsync("<html><body>Hello</body></html>");
                await sw.FlushAsync();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

public async Task Run()
{
    listener.Start();
    Task.Run(() => Listen());
}
ence ladus
  • 62
  • 7
  • How actually are you doing it without `await`? – Camilo Terevinto Feb 09 '18 at 15:26
  • `Task.Delay()` is non-blocking. It creates a `Task` which will complete after the specified delay. If you aren't awaiting it, what are you doing? – Paul Turner Feb 09 '18 at 15:28
  • 1
    "blocking Task.Delay"? What is that? – spender Feb 09 '18 at 15:32
  • You put a 100ms delay in the loop which means only 10 iterations can complete in a second. `Task.Delay()` doesn't affect the bandwidth. The code throttled the listener to only 1 request per 100ms – Panagiotis Kanavos Feb 09 '18 at 15:44
  • I don't know where did you get that sample, but you should have used one from https://stackoverflow.com/questions/9034721/handling-multiple-requests-with-c-sharp-httplistener if you are building server that handles multiple requests in parallel. – Alexei Levenkov Feb 09 '18 at 16:14

3 Answers3

4

This is because the pause you've inserted delays the loop.

You only accept requests on the line:

await listener.GetContextAsync()

which is in this same loop.

This means that you have to wait for the previous request to complete before you accept a new context.

Most likely, you want to handle the accepted contexts concurrently. To achieve this, you should accept contexts in a tight loop, then spin off the handling of the request to a different place which doesn't "hang" the accept loop.

At it's simplest, you could "fire and forget"...

while (listener.IsListening)
{
    var context = await listener.GetContextAsync().ConfigureAwait(false);
    HandleContextAsync(context); //note: not awaited
}

...in reality, you'll need to think a bit harder about how to track exceptions in the HandleContextAsync method.

spender
  • 117,338
  • 33
  • 229
  • 351
  • Sorry for additional answer, I moved context handling to another place and now it's good, but now I have another problem (ironically, this is my initial problem). When I test server with 5000 concurrent requets (-c in Apache Benchmar) it throws **System.Net.HttpListenerException: "An operation was attempted on a nonexistent network connection"**. Without await Task.Delay that not happens. https://pastebin.com/z2Q441zz – ence ladus Feb 09 '18 at 16:07
  • @enceladus this is a completely different question, so just create another one for that. – Evk Feb 09 '18 at 16:19
1

The Delay() Task is created and allowed to run in its own time, without influencing the creating method any further. If you neither await (asynchronous) or Wait() (synchronous) on that Task, then the creating method carries on immediately, making it appear 'fast'. await actually waits for the delay Task to complete, but does it in a way that means the thread does not block and can execute other work concurrently. Your other version doesn't wait at all, synchronously or otherwise. The delay Task is created and runs, but since nothing else cares when it finishes, it just gets executed and garbage-collected playing no further part in the method.

Tom W
  • 5,108
  • 4
  • 30
  • 52
0

That's because you wait 100 ms before processing a next request, so you can process maximum 10 requests per second. Task.Delay without await doesn't affect your application at all, you can remove this line and as a result still will be handle 2000 request per second.

olegk
  • 765
  • 4
  • 13