2016-01-24 2 views
5

У меня есть процесс с одной нитью, который выполняется некоторое время. Мне нужно, чтобы несколько пользователей имели доступ для выполнения этого процесса, и я выбираю протокол http для управления вызовом.Многопоточный ответ для HttpListener

Естественно, когда один процесс работает, все остальные должны ждать, пока это будет сделано. Если процесс доступен, он выполняется. Если нет, тогда отправляется ответ BUSY.

Вот реализация:

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); 
      } 
     } 
    } 
} 

Для тестирования я 2 HTTP вызовов. Я ожидаю, что у меня есть 2 разных ответа WORK и BUSY. Однако я вижу, что второй запрос ждет, прежде чем он закончит, а затем выполнит.

 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 

Что не так в моем понимании, как оно должно работать?

Обновление (слишком много комментариев не приветствуется SO): Мой код выглядит неловко, потому что это репликация реального процесса. В «моем» приложении рабочий процесс - это основной процесс, который имеет «любезность» для запуска встроенного кода C# в определенные моменты. Таким образом, я не могу запускать новую задачу для обработки запроса, и она должна быть асинхронной, поскольку рабочий процесс выполняет свою собственную работу и только вызывает подчиненную часть кода для уведомления клиентов, когда данные доступны. Это асинхронно, потому что код вызывается и должен заканчиваться как можно скорее или он блокирует основное приложение. Я попытаюсь добавить дополнительный поток с синхронным вызовом и посмотреть, как он влияет на ситуацию.

Отладчик не используется в этом примере, чтобы не мешать процессам времени и времени, напечатанным на консоль в режиме реального времени. Отладка велика и необходима, но в этом случае я пытаюсь заменить результат, чтобы избежать использования дополнительного актера в сценарии синхронизации/ожидания.

Само приложение не является тяжелой загруженной беседой. 1-3 клиентов редко запрашивают основную заявку для ответа. http-протокол используется для удобства не для тяжелых или часто разговоров. Похоже, что некоторые браузеры, такие как IE, работают нормально (Windows для Windows-общения?), А некоторые вроде Chrome (более системные агностики) реплицируют поведение моего приложения. Посмотрите на отметки времени, Chrome, IE, IE, Chrome и последний Chrome по-прежнему идут в WORK-процесс. BTW, код изменяется для каждого предложения беседы, и теперь новый запрос помещается сразу после получения предыдущего.

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

enter image description here

Кроме того, следующие предложения, я должен был изменить asyncronious вызов syncroniuos и результат все тот же

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(); 
    } 
} 

ответ

2

Я не полностью прочитал и понял, что код. Это неудобно. Вот очень чистая структура, которую легко получить:

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

Это просто отправляет всю поступающую работу. Затем:

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); 
} 
} 

Это действительно все, что необходимо.

В частности, вам бессмысленно использовать async IO. Я предполагаю, что вы скопировали этот код или идею откуда-то. Общая ошибка. Async IO вообще ничего не помогает вам и запутывает код.

+0

Hm ... это важная информация. Итак, что реально и что подделано в отношении кода, который вы опубликовали? Я не знаю, какие изменения я могу предложить и где искать. – usr

+0

Одна из причин может заключаться в том, что следует вызвать BeginGetContext сразу после EndGC. Поскольку код прямо сейчас, есть много работы, выполненной между ними. – usr

+0

Btw, вы использовали отладчик, чтобы узнать, что произойдет? – usr

4

код, как вывешенные работает точно так же, как он должен:

enter image description here

Не удается воспроизвести. Я думаю, это отвечает на вопрос, потому что, по-видимому, вы неправильно управляете тестовой нагрузкой. Я поехал, многократно нажимая кнопку отправки композитора Fiddler.

Спасибо за публикацию исполняемого кода. Я должен был попробовать это раньше!

+0

У меня было открыто 2 Chromes и нажмите кнопку перезагрузки за один и через 2 секунды в другой. Оба сразу пришли к статусу «Ожидание ответа». Хотите снять скриншот? :) Я согласен - он работает иногда. Даже в большинстве случаев. Причина, по которой я начал копаться, заключается в том, что в производстве мы видим, что это происходит довольно часто. Я предполагаю, что сделал неплохую работу, чтобы извлечь код и показать, что он не работает. Если, конечно, вы не думаете, что это Photoshop, и я с удовольствием смешиваю людей, у которых есть ответы, которые я действительно ценю. – Alex

+0

Хм ... Можете ли вы воспроизвести с помощью Fiddler? Это было бы более чистым способом, который, возможно, легче понять и воспроизвести. Фактически, я получаю такое же поведение с помощью Chrome, но Fiddler показывает, что Chrome делает запросы один за другим. Не знаю, почему, но это не ошибка приложения. – usr

+0

IE ведет себя так, как ожидалось. – usr

Смежные вопросы