3
\$\begingroup\$

I have a program that controls a custom machine. I wanted to add some simple HTTP comms so I could control it from the network or other programs.

My server class is:

public class Server
{
    private volatile bool stop = true;
    private Action<string> methodOne;

    public Server(Action<string> methodOne)
    {
        this.methodOne= methodOne;
    }

    public async Task StartAsync()
    {
        var prefix = "http://localhost:5005/";
        HttpListener listener = new HttpListener();
        listener.Prefixes.Add(prefix);
        try
        {
            listener.Start();
            stop = false;
        }
        catch (HttpListenerException hlex)
        {
            return;
        }
        while (listener.IsListening)
        {
            var context = await listener.GetContextAsync();
            try
            {
                await ProcessRequestAsync(context);
            }
            catch (Exception ex)
            {
                Console.WriteLine("# EXCEPTION #   " + ex.StackTrace);
            }
            if (stop == true) listener.Stop();
        }
        listener.Close();
    }

    public void Stop() 
    {
        stop = true; 
    }

    private async Task ProcessRequestAsync(HttpListenerContext context)
    {
        // Get the data from the HTTP stream
        var body = await new StreamReader(context.Request.InputStream).ReadToEndAsync();            
        HttpListenerRequest request = context.Request;

        if (request.RawUrl.StartsWith("/methodOne"))
        {
            //Get parameters
            var options = context.Request.QueryString;
            var keys = options.AllKeys;
            //Run function
            methodOne("some method parameter");
            //Respond
            byte[] b = Encoding.UTF8.GetBytes("ack");
            context.Response.StatusCode = 200;
            context.Response.KeepAlive = false;
            context.Response.ContentLength64 = b.Length;
            var output = context.Response.OutputStream;
            await output.WriteAsync(b, 0, b.Length);
            context.Response.Close();
        }
    }
}

I initialise the Server by passing it the delegate for the function in the main view model that will be called, then start itwith server.StartAsync();

VS2017 gives me some advice that I didn't await server.StartAsync(); so thread will continue, but that is the functionality I want.

Any problems with making a server like this, using async and having it run on the main thread? All the server does is call methods in the view model, and there will usually be just one connection. The advantage for me is that being all on the same thread I don't have to worry about dispatching etc.

\$\endgroup\$
  • \$\begingroup\$ "...that is the functionality I want." Then, probably, you should not use Task for this. \$\endgroup\$ – Adriano Repetti Jun 12 '18 at 13:15
  • \$\begingroup\$ @AdrianoRepetti please elaborate \$\endgroup\$ – geometrikal Jun 12 '18 at 13:16
  • \$\begingroup\$ Tasks are designed for (somehow) short living operations. They're not the right tool (for many reasons) to setup a long living listener. Why not? Simplifying little bit, one for all because the total number of threads devoted to tasks may not increase over time then you may "limit" the number of tasks available for short living operations (what they're designed for). Increasing pressure also may cause (it's an implementation detail) the number of tasks to increase (which you may not need, wasting resources). Well...if you need a thread-like task...use a Thread instead. \$\endgroup\$ – Adriano Repetti Jun 12 '18 at 13:45
2
\$\begingroup\$

Visual Studio will warn if you don't use the Task object returned from the function, either immediately with an await statement or by storing it in a variable. The reason is that this is the only connection you have with the Task that you have started, and without it there is no way to find out if it has completed or to get the return value.

Stylistically, methodOne is not a good name for that variable, something like handler or callback would make more sense. It also might make sense for the signature to be a little more complex, like accepting a dictionary of parameters and returning an object to serialize to the response.

The other thing to realize is that the called function doesn't know what you do or don't do with the Task, so its behavior can't depend on that. Therefore "using async and having it run on the main thread" does not accurately describe what happens in your situation. The task is run asynchronously on a background task either way, but in your case the main thread does not block on its completion. If you step in with a debugger, you'll see that the code up to the first await (in the while loop) is executed on the calling thread, and at that point control returns to the caller, who could then maintain a reference to the Task if it wants.

As for whether async and Task are right for your job: it sounds like your needs are pretty modest and that there isn't a real argument to be made for or against. If there aren't many requests coming in, it doesn't really matter how you handle them. If there could be a lot coming in and you wanted to limit the impact on the rest of the system, maybe it would make sense to do the work synchronously on a single thread (that you create for that specific purpose).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.