Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multithread answering for HttpListener

I have single thread process which executes some long time. I need several users to have access to execute this process and I choose http protocol to manage invocation.

Naturally, when one process is working everybody else should wait till it's done. If process is available it executes. If not then BUSY answer is sent.

Here is implementation:

using System;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace simplehttp
{
    class Program
    {
        private static System.AsyncCallback task;
        private static System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false);// Notifies one or more waiting threads that an event has occurred. 
        private static HttpListenerContext workingContext = null;

        public static bool isBackgroundWorking()
        {
            return mre.WaitOne(0);
        }
        static void Main(string[] args)
        {
            new Thread(() =>
            {
                Thread.CurrentThread.IsBackground = true;
                while (true)
                {
                    Console.WriteLine("    waitOne " + isBackgroundWorking());
                    mre.WaitOne(); // Blocks the current thread until the current WaitHandle receives a signal.
                    Console.WriteLine("    do job" + " [" + Thread.CurrentThread.Name + ":" + Thread.CurrentThread.ManagedThreadId + " ]\n");
                    HttpListenerRequest request = workingContext.Request;
                    HttpListenerResponse response = workingContext.Response;
                    string responseString = "WORK " + DateTime.Now ;
                    byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
                    response.ContentLength64 = buffer.Length;
                    System.IO.Stream output = response.OutputStream;
                    Thread.Sleep(10000);
                    output.Write(buffer, 0, buffer.Length);
                    output.Close();
                    Console.WriteLine("    " + responseString + "\t" + DateTime.Now);
                    workingContext = null;
                    mre.Reset(); // Sets the state of the event to nonsignaled, causing threads to block.
                }
            }).Start();

            // Create a listener.
            HttpListener listener = new HttpListener();

            listener.Prefixes.Add("http://localhost:6789/index/");
            listener.Start();
            Console.WriteLine("Listening..." + " [" + Thread.CurrentThread.Name + ":" + Thread.CurrentThread.ManagedThreadId + " ]\n");

            task = new AsyncCallback(ListenerCallback);

            IAsyncResult resultM = listener.BeginGetContext(task,listener);
            Console.WriteLine("Waiting for request to be processed asyncronously.");

            Console.ReadKey();
            Console.WriteLine("Request processed asyncronously.");
            listener.Close();
        }

        private static void ListenerCallback(IAsyncResult result)
        {
            HttpListener listener = (HttpListener) result.AsyncState;

            //If not listening return immediately as this method is called one last time after Close()
            if (!listener.IsListening)
                return;

            HttpListenerContext context = listener.EndGetContext(result);
            listener.BeginGetContext(task, listener);

            if (workingContext == null && !isBackgroundWorking())
            {
                // Background work
                workingContext = context;
                mre.Set(); //Sets the state of the event to signaled, allowing one or more waiting threads to proceed.
            }
            else
            {
            HttpListenerRequest request = context.Request;
            HttpListenerResponse response = context.Response;
            string responseString = "BUSY "+ DateTime.Now + " [" + Thread.CurrentThread.Name + ":" + Thread.CurrentThread.ManagedThreadId;
            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
            response.ContentLength64 = buffer.Length;
            System.IO.Stream output = response.OutputStream;
            output.Write(buffer, 0, buffer.Length);
            output.Close();
            Console.WriteLine(responseString + "\t" + DateTime.Now);
            }
        }
    }
}

To test I do 2 http calls. I expect have 2 different answers WORK and BUSY. However I see that second request waits first to finish and then executes.

      waitOne False
Listening... [:10 ]

Waiting for request to be processed asyncronously.
      do job [:11 ]

      WORK 1/24/2016 10:34:01 AM  1/24/2016 10:34:11 AM
      waitOne False
      do job [:11 ]

      WORK 1/24/2016 10:34:11 AM  1/24/2016 10:34:21 AM
      waitOne False

What is wrong in my understanding how it should work?

Update (too many comments are not ecouraged by SO): My code looks awkward because it is replication of real process. In "my" application working process is main process which has has "courtesy" to run embedded C# code at some particular moments. So, I cannot run new task to process request and it must be asyncronious since working process does its own job and only invokes slave piece of code to notify clients when data is available. It is asyncroinious because code is invoked and should finish as soon as possible or it will block master application. I'll try to add additional thread with synchronous call and see hot it affects situation.

Debugger is not used in this example to not interfere with real time process and time stamps printed to Console. Debugging is great and necessary but in this case I try to substitute with output to avoid extra actor in synchronization/waiting scenario.

The application itself is not heavy loaded conversation. 1-3 clients seldom ask main application for answer. http protocol is used for convenience not for heavy or often conversations. It appears that some browsers like IE work fine (Windows to Windows conversation?) and some like Chrome (more system agnostic) replicate my application behavior. Look at the time stamps, Chrome, IE,IE,Chrome and last Chrome still went to WORK process. BTW, code is changed per conversation suggestion and now new request is placed immediately after retrieving previous one.

    HttpListenerContext context = listener.EndGetContext(result);
    listener.BeginGetContext(task, listener); 

enter image description here

Also, following suggestions, I had change asyncronious call to syncroniuos and result is still the same

private static void ListenerCallback(IAsyncResult result)
{
    HttpListener listener = (HttpListener) result.AsyncState;

    //If not listening return immediately as this method is called one last time after Close()
    if (!listener.IsListening)
        return;

    HttpListenerContext context = listener.EndGetContext(result);

    while (true)
    {
        if (workingContext == null && !isBackgroundWorking())
        {
            // Background work
            workingContext = context;
            mre.Set(); //Sets the state of the event to signaled, allowing one or more waiting threads to proceed.
        }
        else
        {
            HttpListenerRequest request = context.Request;
            HttpListenerResponse response = context.Response;
            string responseString = "BUSY " + DateTime.Now + " [" + Thread.CurrentThread.Name + ":" +
                                    Thread.CurrentThread.ManagedThreadId;
            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
            response.ContentLength64 = buffer.Length;
            System.IO.Stream output = response.OutputStream;
            output.Write(buffer, 0, buffer.Length);
            output.Close();
            Console.WriteLine(responseString + "\t" + DateTime.Now);
        }
        context=listener.GetContext();
    }
}
like image 660
Alex Avatar asked Jan 24 '16 15:01

Alex


1 Answers

The code as posted works exactly like it should:

enter image description here

Can't reproduce. I guess that answers the question because apparently you're driving the test workload incorrectly. I drove it by repeatedly clicking the Fiddler composer send button.

Thanks for posting executable code, though. I should have tried that earlier!

like image 113
usr Avatar answered Nov 15 '22 06:11

usr