Как определить более агрессивный тайм-аут для HttpWebRequest?

Внутри переносимой библиотеки классов у меня есть следующий метод, который отправляет данные на определенный URL-адрес. Метод прекрасно работает. Однако я хотел бы указать более агрессивный тайм-аут (по умолчанию 100 секунд).

Учитывая, что в классе HttpWebRequest из переносимой библиотеки классов нет свойства Timeout, как я могу убедиться, что вызов прерывается, если он занимает больше нескольких секунд?

public async Task<HttpResponse> PostAsync(Uri uri, string data)
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.Method = "POST";
    request.ContentType = "application/x-www-form-urlencoded";

    using (Stream requestStream = await request.GetRequestStreamAsync())
    {
        byte[] postBytes = Encoding.UTF8.GetBytes(data);
        requestStream.Write(postBytes, 0, postBytes.Length);
    }

    HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
    return new HttpResponse(response.StatusCode, await new StreamReader(response.GetResponseStream()).ReadToEndAsync());
}

person Martin    schedule 12.12.2012    source источник
comment
HttpWebRequest.Timeout = 20000; ? это достаточно умно?   -  person Parimal Raj    schedule 28.12.2012


Ответы (2)


Приведенный ниже код либо вернет HttpWebResponse, либо null, если время ожидания истекло.

HttpWebResponse response = await TaskWithTimeout(request.GetResponseAsync(), 100);
if(response != null)
{
  ....
}

Task<HttpWebResponse> TaskWithTimeout(Task<WebResponse> task, int duration)
{
    return Task.Factory.StartNew(() =>
    {
        bool b = task.Wait(duration);
        if (b) return (HttpWebResponse)task.Result;
        return null;
    });
}

--ИЗМЕНИТЬ--

Создание метода расширения было бы еще лучше

public static class SOExtensions
{
    public static Task<T> WithTimeout<T>(this Task<T> task, int duration)
    {
        return Task.Factory.StartNew(() =>
        {
            bool b = task.Wait(duration);
            if (b) return task.Result;
            return default(T);
        });
    }
}

Использование будет:

var response = (HttpWebResponse)await request.GetResponseAsync().WithTimeout(1000);

--ИЗМЕНИТЬ 2--

Другой способ сделать это

public async static Task<T> WithTimeout<T>(this Task<T> task, int duration)
{
    var retTask = await Task.WhenAny(task, Task.Delay(duration))
                            .ConfigureAwait(false);

    if (retTask is Task<T>) return task.Result;
    return default(T);
}
person L.B    schedule 12.12.2012
comment
Ух ты! Интересная идея! Мне нравится это, потому что это элегантно и относительно легко понять. Но мне интересно, является ли task.Wait блокирующей операцией (т. е. поток будет приостановлен до тех пор, пока он не вернется). - person Martin; 12.12.2012
comment
@Martin Да, это блокирующая операция, но она выполняется в другой задаче (см.: Task.Factory.StartNew) и ничем не отличается от вызова await request.GetResponseAsync() - person L.B; 12.12.2012
comment
Я согласен! Это всего лишь еще один поток в ThreadPool. Интересно, сможем ли мы сделать его неблокирующим. Блокировать или нет, это очень умное решение. - person Martin; 12.12.2012
comment
Это ОЧЕНЬ ПЛОХАЯ идея по двум причинам. 1. Он использует поток для каждого вызова. Если у вас есть 1000 параллельных вызовов, у вас будет 1000 потоков в состоянии ожидания. Я лично загнал систему на землю с помощью этой техники. 2. Когда время операции истекает, веб-запрос и базовое TCP-соединение остаются открытыми навсегда. Я лично сгонял другую систему :) по именно такой методике. - person Alon Catz; 19.02.2014
comment
@Alon-Catz 1. Он использует TASK для каждого вызова. Задачи — это не потоки. TPL попытается разделить задачи на оптимальное количество потоков для используемой системы. 2. Я согласен с тем, что WebResponse нужно правильно утилизировать. Это не значит, что это плохое решение. Просто добавьте оператор using: using(var response = (HttpWebResponse)await request.GetResponseAsync().WithTimeout(1000)) { } - person Jop; 01.05.2014
comment
Вы можете попробовать его и опубликовать результаты здесь. Мои уроки были усвоены трудным путем. Я просто хотел поделиться. - person Alon Catz; 01.05.2014
comment
Третья часть самая простая для понимания, имеет больше смысла. - person Adarsha; 15.04.2015
comment
@Jop @Alon Правильная утилизация ответа, похоже, не решает проблему, когда TCP-соединение остается открытым навсегда, что я также видел. По моему опыту, вам нужно позвонить request.Abort(). Возможно, чтение до конца потока асинхронного ответа будет иметь тот же эффект - я не знаю прямо сейчас. Достаточно ли этого для решения второго пункта Алона? Если я не прерву запрос, даже после успеха, я получу Task, который просто сидит с Status из WaitingForActivation, даже в новых экземплярах HttpWebRequest. - person Eric Lease; 27.01.2016

// Abort the request if the timer fires. 
private static void TimeoutCallback(object state, bool timedOut) { 
    if (timedOut) {
        HttpWebRequest request = state as HttpWebRequest;
            if (request != null) {
              request.Abort();
        }
    }
}

Да, клиентское приложение несет ответственность за реализацию собственного механизма тайм-аута. Вы можете сделать это из приведенного выше кода, который устанавливает время ожидания и использует метод ThreadPool.RegisterWaitForSingleObject. См. http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.abort.aspx для получения полной информации. По сути, он прерывает GetResponse, BeginGetResponse, EndGetResponse, GetRequestStream, BeginGetRequestStream или EndGetRequestStream.

person mashtheweb    schedule 12.12.2012