8

Wewnątrz przenośnej biblioteki klas, mam następującą metodę, która publikuje dane do określonego adresu URL. Metoda działa świetnie. Jednak chciałbym podać bardziej agresywny limit czasu (domyślnie jest to 100 sekund).Jak zdefiniować bardziej agresywny limit czasu dla HttpWebRequest?

Biorąc pod uwagę, że nie ma właściwości Timeout w klasie HttpWebRequest z biblioteki klas przenośnych, w jaki sposób mogę się upewnić, że połączenie zostanie przerwane, jeśli trwa dłużej niż kilka sekund?

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

HttpWebRequest.Timeout = 20000; ? to jest wystarczająco inteligentne? –

Odpowiedz

20

Poniższy kod również zwróci HttpWebResponse lub wartość null, jeśli upłynął limit czasu.

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

--EDIT--

Tworzenie metodę rozszerzenia byłoby jeszcze lepiej

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

Wykorzystanie byłoby:

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

--edit 2--

Kolejny sposób to zrobić

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

Wow! Ciekawy pomysł! Podoba mi się, ponieważ jest elegancki i stosunkowo łatwy do zrozumienia. Zastanawiam się jednak, czy task.Wait jest operacją blokującą (tzn. Wątek zostanie wstrzymany, aż się zwróci). – Martin

+1

@Martin Tak, jest to operacja blokująca, ale wykonywana jest w innym zadaniu (zobacz: 'Task.Factory.StartNew') i nie będzie się różnić od wywoływania' oczekuj na request.GetResponseAsync() ' –

+0

Zgadzam się! To tylko jeden wątek w ThreadPool. Zastanawiam się, czy moglibyśmy zrobić to bez blokowania. Blokowanie czy nie, to bardzo sprytne rozwiązanie. – Martin

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

Tak to jest w gestii aplikacji klienckiej do wykonywania własnego mechanizmu time-out. Możesz to zrobić z powyższego kodu, który ustawia limit czasu i używa metody ThreadPool.RegisterWaitForSingleObject. Aby uzyskać szczegółowe informacje, patrz http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.abort.aspx. Zasadniczo powoduje przerwanie działania GetResponse, BeginGetResponse, EndGetResponse, GetRequestStream, BeginGetRequestStream lub EndGetRequestStream.