Как указать значение тайм-аута в HttpWebRequest.BeginGetResponse без блокировки потока - PullRequest
3 голосов
/ 23 января 2010

Я пытаюсь отправлять веб-запросы асинхронно. Мой код работает нормально, за исключением одной вещи: встроенного способа указания времени ожидания для BeginGetResponse не существует. Пример MSDN ясно показывает рабочий пример, но недостатком является то, что все они заканчиваются

SomeObject.WaitOne()

Который снова ясно заявляет, что он блокирует поток. Я буду в среде с высокой нагрузкой и не смогу блокировать, но мне также нужно тайм-аут запроса, если он занимает более 2 секунд. Если не считать создание отдельного пула потоков и управление им, есть ли в фреймворке что-то, что может мне помочь?

Исходные примеры:

Мне бы хотелось, чтобы асинхронный обратный вызов на BeginGetResponse() вызывался после истечения срока действия моего параметра timeout с некоторым указанием на то, что тайм-аут произошел.

Кажущийся очевидным параметр TimeOut не учитывается при асинхронных вызовах. Параметр ReadWriteTimeout не вступает в игру, пока не вернется ответ. Непатентованное решение было бы предпочтительным.

EDIT:

Вот что я придумала: после вызова BeginGetResponse я создаю Timer с моей продолжительностью, и это конец фазы «начала» обработки. Теперь либо запрос будет завершен, и моя «конечная» фаза будет вызвана, либо истекает период ожидания.

Чтобы определить гонку и получить единственного победителя, я называю увеличение счетчика «завершено» потокобезопасным способом. Если «тайм-аут» - это первое возвращаемое событие, я отменяю запрос и останавливаю таймер. В этой ситуации, когда вызывается «конец», EndGetResponse выдает ошибку. Если фаза «конец» наступает первой, счетчик увеличивается, а «тайм-аут» отменяет прерывание запроса.

Кажется, это работает так, как я хочу, и одновременно предоставляет настраиваемое время ожидания. Недостатком является дополнительный объект таймера и обратные вызовы, которых я стараюсь избегать. Я вижу 1-3 потока, обрабатывающих различные части (начало, время ожидания, конец), поэтому кажется, что это работает. И у меня нет никаких звонков "ожидания".

Я пропустил слишком много сна или нашел способ обслуживать свои запросы без блокировки?

int completed = 0;

this.Request.BeginGetResponse(GotResponse, this.Request);
this.timer = new Timer(Timedout, this, TimeOutDuration, Timeout.Infinite);

private void Timedout(object state)
{
    if (Interlocked.Increment(ref completed) == 1)
    {
        this.Request.Abort();
    }
    this.timer.Change(Timeout.Infinite, Timeout.Infinite);
    this.timer.Dispose();
}

private void GotRecentSearches(IAsyncResult result)
{
    Interlocked.Increment(ref completed);
}

Ответы [ 4 ]

0 голосов
/ 30 июля 2014

Если вы можете использовать async / await, то

private async Task<WebResponse> getResponseAsync(HttpWebRequest request)
        {
            var responseTask = Task.Factory.FromAsync(request.BeginGetResponse, ar => (HttpWebResponse)request.EndGetResponse(ar), null);
            var winner = await (Task.WhenAny(responseTask, Task.Delay(new TimeSpan(0, 0, 20))));
            if (winner != responseTask)
            {
                throw new TimeoutException();
            }
            return await responseTask;
        }
0 голосов
/ 23 января 2010

Что это за приложение? Это сервисный процесс / веб-приложение / консольное приложение?

Как вы создаете свою рабочую нагрузку (т.е. запросы)? Если у вас есть очередь работы, которую необходимо выполнить, вы можете начать с «N» числа асинхронных запросов (с созданной вами структурой для тайм-аутов), а затем, когда каждый запрос завершится (либо с таймаутом, либо с успехом), вы может получить следующий запрос из очереди.

Таким образом, это станет моделью производителя / потребителя.

Таким образом, если вы сконфигурируете свое приложение так, чтобы не превышало «N» запросов, вы можете поддерживать пул «N» таймеров, которые вы повторно используете (не распределяя) между запросами.

Или, альтернативно, вы можете использовать ThreadPool.SetTimerQueueTimer () для управления вашими таймерами. Пул потоков будет управлять таймерами для вас и повторно использовать таймер между запросами.

Надеюсь, это поможет.

0 голосов
/ 26 февраля 2010

Похоже, мой оригинальный подход - лучшая из доступных вещей.

0 голосов
/ 23 января 2010

Вы можете использовать BackgroundWorker для запуска вашего HttpWebRequest в отдельном потоке, так что ваш основной поток все еще жив. Таким образом, этот фоновый поток будет заблокирован, но первый - нет.

В этом контексте вы можете использовать ManualResetEvent.WaitOne(), как в этом примере: HttpWebRequest.BeginGetResponse() метод.

...