15

I have a .NET Windows Service which spawns a thread that basically just acts as an HttpListener. This is working fine in synchronous mode example...

private void CreateLListener()
{
    HttpListenerContext context = null;
    HttpListener listener = new HttpListener();
    bool listen = true;

    while(listen)
    {
        try
        {
            context = listener.GetContext();
        }
        catch (...)
        {
            listen = false;
        }
        // process request and make response
    }
}

The problem I now have is I need this to work with multiple requests and have them responded to simultaneously or at least in an overlapped way.

To explain further - the client is a media player app which starts by making a request for a media file with the request header property Range bytes=0-. As far as I can tell it does this to work out what the media container is.

After it has read a 'chunk' (or if it has read enough to ascertain media type) it then makes another request (from a different client socket number) with Range bytes=X-Y. In this case Y is the Content-Length returned in the first response and X is 250000 bytes less than that (discovered using IIS as a test). At this stage it is getting the last 'chunk' to see if it can get a media time-stamp to gauge length.

Having read that, it makes another request with Range bytes=0- (from another socket number) to start streaming the media file properly.

At any time though, if the user of the client performs a 'skip' operation it then sends another request (from yet another socket number) with Range bytes=Z- where Z is the position to jump to in the media file.

I'm not very good with HTTP stuff but as far as I can tell I need to use multiple threads to handle each request/response while allowing the original HttpListener to return to listening. I've done plenty of searching but can't find a model which seems to fit.

EDIT:

Acknowledgement and gratitude to Rick Strahl for the following example which I was able to adapt to suit my needs...

Add a Web Server to your .NET 2.0 app with a few lines of code

Squonk
  • 48,735
  • 19
  • 103
  • 135

3 Answers3

21

If you're here from the future and trying to handle multiple concurrent requests with a single thread using async/await..

public async Task Listen(string prefix, int maxConcurrentRequests, CancellationToken token)
{
    HttpListener listener = new HttpListener();
    listener.Prefixes.Add(prefix);
    listener.Start();

    var requests = new HashSet<Task>();
    for(int i=0; i < maxConcurrentRequests; i++)
        requests.Add(listener.GetContextAsync());

    while (!token.IsCancellationRequested)
    {
        Task t = await Task.WhenAny(requests);
        requests.Remove(t);

        if (t is Task<HttpListenerContext>)
        {
            var context = (t as Task<HttpListenerContext>).Result;
            requests.Add(ProcessRequestAsync(context));
            requests.Add(listener.GetContextAsync());
        }
    }
}

public async Task ProcessRequestAsync(HttpListenerContext context)
{
    ...do stuff...
}
gordy
  • 9,360
  • 1
  • 31
  • 43
  • 1
    If you care about Exceptions from ProcessRequestAsync, and you should care, do not forget to call t.Wait() in an else branch. All in all I like this approach +1. – frast Dec 08 '15 at 10:09
  • Another problem is that the while condition is only checked if one of the WhenAny tasks completes. – frast Dec 08 '15 at 10:29
  • can you elaborate on why only checking on WhenAny is a problem @frast? – LightLabyrinth Mar 07 '17 at 15:55
  • 2
    @LightLabyrinth cancellation only happens after a request is received. If there is no request WhenAny can block forever when no request comes in. – frast Mar 11 '17 at 18:10
  • You could add a try/catch with token.ThrowIfCancellationRequested(); inside the while loop. Then handle the OperationCanceledException, after that, the while loop should exit since the cancelation was requested. – Jim Wolff Jul 06 '17 at 00:59
  • @FRoZeN That won't change a thing. – nphx Dec 20 '17 at 14:19
  • @nphx then i might be misunderstanding something, could you elaborate? – Jim Wolff Dec 21 '17 at 13:21
  • @FRoZeN It will still behave exactly the same way - if there are no requests coming in, `token.ThrowIfCancellationRequested()` will never get to execute. None of the `GetContextAsync()` finish and so `await Task.WhenAny(requests);` keeps blocking. – nphx Dec 21 '17 at 15:13
  • @nphx i think what tripped me up was, normally i think about `When()` calls as non-blocking and only the `Wait()` calls block – Jim Wolff Dec 22 '17 at 09:42
  • can please provide sample code inside 'ProcessRequestAsync' method? – Somnath Kadam Feb 04 '19 at 14:52
19

If you need a more simple alternative to BeginGetContext, you can merely queue jobs in ThreadPool, instead of executing them on the main thread. Like such:

private void CreateLListener() {
    //....
    while(true) {
        ThreadPool.QueueUserWorkItem(Process, listener.GetContext());    
    }
}
void Process(object o) {
    var context = o as HttpListenerContext;
    // process request and make response
}
user1096188
  • 1,809
  • 12
  • 11
13

You need to use the async method to be able to process multiple requests. So you would use e BeginGetContext and EndGetContext methods.

Have a look here.

The synchronous model is appropriate if your application should block while waiting for a client request and if you want to process only one *request at a time*. Using the synchronous model, call the GetContext method, which waits for a client to send a request. The method returns an HttpListenerContext object to you for processing when one occurs.

Aliostad
  • 80,612
  • 21
  • 160
  • 208
  • Async works differently to sync (obviously). The call to begin will immediately return, so you need to cope with that, hence most apps have some sort of waitone. That won't block though. The callback you specified when you called begin will execute on a new thread. Within there you call end followed by begin again to keep listening & then you do your work. new requests are handled on new threads until you decide to stop listening. – Simon Halsey Jan 27 '12 at 15:30
  • 4
    @MisterSquonk pleased that it worked for you. We dont give fish here, but teach fishing. :) – Aliostad Jan 29 '12 at 13:24