Multithreaded answer for HttpListener

I have a single thread process that has been running for a while. I need several users to have access to complete this process, and I choose the http protocol for call control.

Naturally, when one process is running, everyone else should wait until this happens. If a process is available, it runs. If not, then a BUSY response is sent.

Here is the 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 check that I am making 2 http calls. I expect that I have 2 different answers WORK and BUSY. However, I see that the second request waits before it finishes 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 of how this should work?

Update (too many comments not encouraged by SO): My code looks awkward because it is a replication of the real process. In a β€œmy” application, a workflow is the main process that has the β€œcivility” to run C # inline code at certain points. Thus, I cannot start a new task to process the request, and it should be asynchronous, since the workflow does its own work and only calls the subordinate part of the code to notify clients when data is available. This is asynchronous because the code is called and should end as soon as possible or it blocks the main application. I will try to add an additional thread with a synchronous call and see how it affects the situation.

The debugger is not used in this example so as not to interfere with the real-time technology and time stamps printed on the console. Debugging is great and necessary, but in this case I am trying to replace the output to avoid using an extra actor in a synchronization / wait script.

The application itself is not a heavy loaded conversation. 1-3 clients rarely request a basic application for a response. The http protocol is used for convenience not for heavy or frequent conversations. It seems that some browsers, such as IE, are working fine (Windows for Windows communication?), And some like Chrome (more system agnostics) replicate the behavior of my application. Look at the timestamps, Chrome, IE, IE, Chrome and the last Chrome still go to the WORK process. BTW, the code is changed for each sentence of the conversation, and now a new request is placed immediately after receiving the previous one.

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

enter image description here

In addition to the following sentences, I changed the syncroniuos asynchronous call, and the 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(); } } 
+5
source share
2 answers

The above code works exactly as it should:

enter image description here

Unable to play. I think this answers the question, because, apparently, you are incorrectly managing the test load. I drove by repeatedly pressing the submit button of the composer Fiddler.

Thanks for posting the executable code. I should have tried this before!

+4
source

I did not fully read and understand this code. This structure is inconvenient. Here is a much cleaner structure that is easily obtained:

 while (true) { var ctx = GetContext(); Task.Run(() => ProcessRequest(ctx)); } 

It just sends all incoming work. Then:

 object @lock = new object(); //is work being done? void ProcessRequest(Context ctx) { if (!Monitor.Enter(@lock)) SendBusy(); else { ProcessInner(ctx); //actually do some work Monitor.Exit(@lock); } } 

That is really all that is needed.

In particular, it makes no sense for you to use async IO. I assume you copied this code or idea from somewhere. Common mistake. Async IO helps you nothing and overthrows the code.

+2
source

Source: https://habr.com/ru/post/1241359/


All Articles