Jak zmienić limit czasu w obiekcie .NET WebClient

230

Próbuję pobrać dane klienta na moją maszynę lokalną (programowo), a ich serwer WWW działa bardzo, bardzo wolno, co powoduje przekroczenie limitu czasu w moim WebClientobiekcie.

Oto mój kod:

WebClient webClient = new WebClient();

webClient.Encoding = Encoding.UTF8;
webClient.DownloadFile(downloadUrl, downloadFile);

Czy istnieje sposób ustawienia nieskończonego limitu czasu dla tego obiektu? A jeśli nie, to czy ktoś może mi pomóc z przykładem alternatywnego sposobu na zrobienie tego?

Adres URL działa poprawnie w przeglądarce - wyświetlenie zajmuje tylko około 3 minut.

Ryall
źródło

Odpowiedzi:

378

Możesz wydłużyć limit czasu: odziedziczyć oryginalną klasę WebClient i przesłonić moduł pobierania żądań sieci, aby ustawić własny limit czasu, jak w poniższym przykładzie.

MyWebClient był w moim przypadku prywatną klasą:

private class MyWebClient : WebClient
{
    protected override WebRequest GetWebRequest(Uri uri)
    {
        WebRequest w = base.GetWebRequest(uri);
        w.Timeout = 20 * 60 * 1000;
        return w;
    }
}
kisp
źródło
5
jaki jest domyślny limit czasu?
knocte
23
Domyślny limit czasu to 100 sekund. Chociaż wydaje się działać przez 30 sekund.
Carter Medlin,
2
Trochę łatwiej jest ustawić Limit czasu za pomocą przedziału czasu TimeSpan.FromSeconds (20). Milisekundy ... ale świetne rozwiązanie!
webwires
18
@webwires Należy używać, .TotalMillisecondsa nie .Milliseconds!
Alexander Galkin
79
Nazwą klasy powinno być: PatientWebClient;)
Jan Willem B
27

Pierwsze rozwiązanie nie działało dla mnie, ale oto kod, który działał dla mnie.

    private class WebClient : System.Net.WebClient
    {
        public int Timeout { get; set; }

        protected override WebRequest GetWebRequest(Uri uri)
        {
            WebRequest lWebRequest = base.GetWebRequest(uri);
            lWebRequest.Timeout = Timeout;
            ((HttpWebRequest)lWebRequest).ReadWriteTimeout = Timeout;
            return lWebRequest;
        }
    }

    private string GetRequest(string aURL)
    {
        using (var lWebClient = new WebClient())
        {
            lWebClient.Timeout = 600 * 60 * 1000;
            return lWebClient.DownloadString(aURL);
        }
    }
Davinci Jeremie
źródło
21

Musisz użyć HttpWebRequestzamiast, WebClientponieważ nie możesz ustawić limitu czasu WebClientbez przedłużania go (nawet jeśli używa HttpWebRequest). Użycie HttpWebRequestzamiast tego pozwoli ci ustawić limit czasu.

Fenton
źródło
To nie jest prawda ... możesz zobaczyć powyżej, że nadal możesz używać WebClient, choć niestandardowa implementacja zastępuje WebRequest, aby ustawić limit czasu.
DomenicDatti
7
„System.Net.HttpWebRequest.HttpWebRequest ()” jest przestarzały: „Ten interfejs API obsługuje infrastrukturę .NET Framework i nie jest przeznaczony do użycia bezpośrednio z kodu”
przydatneBee
3
@ usefulBee - ponieważ nie powinieneś wywoływać tego konstruktora: "Do not use the HttpWebRequest constructor. Use the WebRequest.Create method to initialize new HttpWebRequest objects."z msdn.microsoft.com/en-us/library/… . Zobacz także stackoverflow.com/questions/400565/…
ToolmakerSteve
Wyjaśnij: Chociaż należy unikać tego konkretnego konstruktora (i tak nie jest już częścią nowszych wersji .NET), korzystanie z Timeoutwłaściwości programu HttpWebRequest. Jest w milisekundach.
Marcel,
10

Nie można uruchomić kodu w.Timeout po wyciągnięciu kabla sieciowego, po prostu nie upłynął limit czasu, został przeniesiony do używania HttpWebRequest i wykonuje teraz zadanie.

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(downloadUrl);
request.Timeout = 10000;
request.ReadWriteTimeout = 10000;
var wresp = (HttpWebResponse)request.GetResponse();

using (Stream file = File.OpenWrite(downloadFile))
{
    wresp.GetResponseStream().CopyTo(file);
}
themullet
źródło
1
Ta odpowiedź działa świetnie, ale dla wszystkich zainteresowanych, jeśli użyjesz var wresp = await request.GetResponseAsync();zamiast var wresp = (HttpWebResponse)request.GetResponse();, dostaniesz ponownie ogromny limit czasu
andrewjboyd
andrewjboyd: czy wiesz, dlaczego GetResponseAsync () nie działa?
osexpert,
9

Dla kompletności, oto rozwiązanie kisp przeniesione do VB (nie można dodać kodu do komentarza)

Namespace Utils

''' <summary>
''' Subclass of WebClient to provide access to the timeout property
''' </summary>
Public Class WebClient
    Inherits System.Net.WebClient

    Private _TimeoutMS As Integer = 0

    Public Sub New()
        MyBase.New()
    End Sub
    Public Sub New(ByVal TimeoutMS As Integer)
        MyBase.New()
        _TimeoutMS = TimeoutMS
    End Sub
    ''' <summary>
    ''' Set the web call timeout in Milliseconds
    ''' </summary>
    ''' <value></value>
    Public WriteOnly Property setTimeout() As Integer
        Set(ByVal value As Integer)
            _TimeoutMS = value
        End Set
    End Property


    Protected Overrides Function GetWebRequest(ByVal address As System.Uri) As System.Net.WebRequest
        Dim w As System.Net.WebRequest = MyBase.GetWebRequest(address)
        If _TimeoutMS <> 0 Then
            w.Timeout = _TimeoutMS
        End If
        Return w
    End Function

End Class

End Namespace
GlennG
źródło
7

Jak mówi Sohnee, używanie System.Net.HttpWebRequesti ustawianie Timeoutwłaściwości zamiast używania System.Net.WebClient.

Nie można jednak ustawić nieskończonej wartości limitu czasu (nie jest obsługiwana, a próba zrobienia tego spowoduje wygenerowanie an ArgumentOutOfRangeException).

Polecam najpierw wykonać żądanie HEAD HTTP i zbadać Content-Lengthzwróconą wartość nagłówka, aby określić liczbę bajtów w pobieranym pliku, a następnie odpowiednio ustawić wartość limitu czasu dla następnego GETżądania lub po prostu określić bardzo długą wartość limitu czasu, którą nigdy nie oczekuj, że przekroczysz.

dariom
źródło
7
'CORRECTED VERSION OF LAST FUNCTION IN VISUAL BASIC BY GLENNG

Protected Overrides Function GetWebRequest(ByVal address As System.Uri) As System.Net.WebRequest
            Dim w As System.Net.WebRequest = MyBase.GetWebRequest(address)
            If _TimeoutMS <> 0 Then
                w.Timeout = _TimeoutMS
            End If
            Return w  '<<< NOTICE: MyBase.GetWebRequest(address) DOES NOT WORK >>>
        End Function
Josh
źródło
5

Dla każdego, kto potrzebuje WebClient z limitem czasu, który działa w przypadku metod asynchronicznych / zadań , sugerowane rozwiązania nie będą działać. Oto, co działa:

public class WebClientWithTimeout : WebClient
{
    //10 secs default
    public int Timeout { get; set; } = 10000;

    //for sync requests
    protected override WebRequest GetWebRequest(Uri uri)
    {
        var w = base.GetWebRequest(uri);
        w.Timeout = Timeout; //10 seconds timeout
        return w;
    }

    //the above will not work for async requests :(
    //let's create a workaround by hiding the method
    //and creating our own version of DownloadStringTaskAsync
    public new async Task<string> DownloadStringTaskAsync(Uri address)
    {
        var t = base.DownloadStringTaskAsync(address);
        if(await Task.WhenAny(t, Task.Delay(Timeout)) != t) //time out!
        {
            CancelAsync();
        }
        return await t;
    }
}

I napisał o pełnej obejście tutaj

Alex
źródło
4

Stosowanie:

using (var client = new TimeoutWebClient(TimeSpan.FromSeconds(10)))
{
    return await client.DownloadStringTaskAsync(url).ConfigureAwait(false);
}

Klasa:

using System;
using System.Net;

namespace Utilities
{
    public class TimeoutWebClient : WebClient
    {
        public TimeSpan Timeout { get; set; }

        public TimeoutWebClient(TimeSpan timeout)
        {
            Timeout = timeout;
        }

        protected override WebRequest GetWebRequest(Uri uri)
        {
            var request = base.GetWebRequest(uri);
            if (request == null)
            {
                return null;
            }

            var timeoutInMilliseconds = (int) Timeout.TotalMilliseconds;

            request.Timeout = timeoutInMilliseconds;
            if (request is HttpWebRequest httpWebRequest)
            {
                httpWebRequest.ReadWriteTimeout = timeoutInMilliseconds;
            }

            return request;
        }
    }
}

Ale polecam bardziej nowoczesne rozwiązanie:

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

public static async Task<string> ReadGetRequestDataAsync(Uri uri, TimeSpan? timeout = null, CancellationToken cancellationToken = default)
{
    using var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
    if (timeout != null)
    {
        source.CancelAfter(timeout.Value);
    }

    using var client = new HttpClient();
    using var response = await client.GetAsync(uri, source.Token).ConfigureAwait(false);

    return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}

Po OperationCanceledExceptionupływie limitu czasu zostanie wyrzucony .

Konstantin S.
źródło
Nie działało, metoda asynchroniczna nadal działa w nieskończoność
Alex
Być może problem jest inny i musisz użyć ConfigureAwait (false)?
Konstantin S.,
-1

W niektórych przypadkach konieczne jest dodanie agenta użytkownika do nagłówków:

WebClient myWebClient = new WebClient();
myWebClient.DownloadFile(myStringWebResource, fileName);
myWebClient.Headers["User-Agent"] = "Mozilla/4.0 (Compatible; Windows NT 5.1; MSIE 6.0) (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)";

To było rozwiązanie mojej sprawy.

Kredyt:

http://genjurosdojo.blogspot.com/2012/10/the-remote-server-returned-error-504.html

antoine
źródło