2016-04-19 13 views
1

У меня проблема. В приведенном ниже коде используется шаблон async/await с HttpListener. Когда запрос отправляется через HTTP «delay», запрашивается строковый аргумент запроса, и его значение заставляет сервер задерживать указанную обработку запроса за данный период. Мне нужен сервер для обработки ожидающих запросов даже после того, как сервер перестает получать новые запросы.Как предотвратить HttpListener от прерывания ожидающих запросов при остановке? HttpListener.Stop не работает

static void Main(string[] args) 
{ 
    HttpListener httpListener = new HttpListener(); 

    CountdownEvent sessions = new CountdownEvent(1); 
    bool stopRequested = false; 

    httpListener.Prefixes.Add("http://+:9000/GetData/"); 

    httpListener.Start(); 

    Task listenerTask = Task.Run(async() => 
    { 
     while (true) 
     { 
      try 
      { 
       var context = await httpListener.GetContextAsync(); 

       sessions.AddCount(); 

       Task childTask = Task.Run(async() => 
       { 
        try 
        { 
         Console.WriteLine($"Request accepted: {context.Request.RawUrl}"); 

         int delay = int.Parse(context.Request.QueryString["delay"]); 

         await Task.Delay(delay); 

         using (StreamWriter sw = new StreamWriter(context.Response.OutputStream, Encoding.Default, 4096, true)) 
         { 
          await sw.WriteAsync("<html><body><h1>Hello world</h1></body></html>"); 
         } 

         context.Response.Close(); 
        } 
        finally 
        { 
         sessions.Signal(); 
        } 
       }); 
      } 
      catch (HttpListenerException ex) 
      { 
       if (stopRequested && ex.ErrorCode == 995) 
       { 
        break; 
       } 

       throw; 
      } 
     } 
    }); 

    Console.WriteLine("Server is running. ENTER to stop..."); 

    Console.ReadLine(); 

    sessions.Signal(); 

    stopRequested = true; 

    httpListener.Stop(); 

    Console.WriteLine("Stopped accepting requests. Waiting for the pendings..."); 

    listenerTask.Wait(); 

    sessions.Wait(); 

    Console.WriteLine("Finished"); 

    Console.ReadLine(); 

    httpListener.Close(); 
} 

Точная проблема здесь в том, что когда сервер остановлен HttpListener.Stop называется, но все ожидающие запросы прерываются немедленно, то есть код не может отправить ответы обратно.

В шаблоне non-async/await (т.е. простая реализация на основе Thread) У меня есть выбор, чтобы прервать поток (который, я полагаю, очень плохой), и это позволит мне обрабатывать ожидающие запросы, потому что это просто отменяет HttpListener. Вызов GetContext.

Не могли бы вы указать мне, что я делаю неправильно и как я могу предотвратить HttpListener для отмены ожидающих запросов в шаблоне async/await?

+0

Что я имел в виду, так это то, что мой сервер перестанет получать новые запросы и сможет обрабатывать ожидающие запросы. Но вызов HttpListener.Stop не позволяет мне отвечать на ожидающие запросы. Что касается CancellationToken в сценарии async/await, как это может мне помочь, если HttpListener.GetContextAsync вообще не принимает CancellationToken? – Rauf

+0

@ Luaan, кстати, спасибо, я обновил текст и заголовок вопроса. – Rauf

+0

@ Luaan, кажется, ошибка. Не удалось заставить HttpListener приостановить прием запросов и обработать ожидающие. Даже попробовал пару Begin/EndGetContext. Еще не повезло. Фактически метод Thread.Abort помогает, поскольку он способен прервать вызов HttpListener.GetContext, не останавливая логику обработки запроса. – Rauf

ответ

2

Похоже, что когда HttpListener закрывает дескриптор очереди запросов, выполняемые запросы прерываются. Насколько я могу судить, нет никакого способа избежать того, чтобы HttpListener это сделал - видимо, это совместимость. В любом случае, именно так работает его система GetContext - когда дескриптор закрыт, собственный метод GetContext вызов для фактического получения контекста запроса немедленно возвращает ошибку.

Thread.Abort не помогает - действительно, мне еще нужно увидеть место, где Thread.Abort используется правильно за пределами сценария «разгрузка домена». Thread.Abort может только прервать код. Так как ваш код в настоящее время работает родной, она будет прервана только тогда, когда он возвращается в управляемый код - это почти точно соответствует просто сделать это:

var context = await httpListener.GetContextAsync(); 

if (stopRequested) return; 

... и так как нет лучше отмены API для HttpListener , это действительно ваш единственный вариант, если вы хотите придерживаться HttpListener.

Останов будет выглядеть следующим образом:

stopRequested = true; 
sessions.Wait(); 

httpListener.Dispose(); 
listenerTask.Wait(); 

Я также предлагаю использовать CancellationToken вместо bool флага - он обрабатывает все беды синхронизации для вас. Если по какой-то причине это нежелательно, убедитесь, что вы синхронизировали доступ к флагу - контрактно, компилятору разрешено пропустить проверку, так как невозможно изменить флаг в однопоточном коде.

Если вы хотите, вы можете сделать listenerTask полный раньше, отправив фиктивный запрос HTTP на себя сразу же после установки stopRequested - это заставит GetContext немедленно вернуться с новым запросом, и вы можете вернуться. Это подход, который обычно используется при работе с API, которые не поддерживают «хорошую» отмену, например. UdpClient.Receive.

+0

Я выбрал несколько другую тактику. Я отменяю токен отмены, а затем ожидаю ожидающих запросов, фактически не останавливая получение новых. Если новый запрос поступит после отмены, я просто прерываю его, а в случаях, когда не поступит дополнительный запрос, и GetContextAsync зависает, я в конечном итоге вызываю Stop and Close на HTTP-прослушиватель. – Rauf