Como alterar o tempo limite em um objeto .NET WebClient

230

Estou tentando baixar os dados de um cliente para minha máquina local (programaticamente) e o servidor da Web é muito, muito lento, o que está causando um tempo limite no meu WebClientobjeto.

Aqui está o meu código:

WebClient webClient = new WebClient();

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

Existe uma maneira de definir um tempo limite infinito nesse objeto? Ou, se não, alguém pode me ajudar com um exemplo de uma maneira alternativa de fazer isso?

O URL funciona bem em um navegador - leva apenas 3 minutos para mostrar.

Ryall
fonte

Respostas:

378

Você pode estender o tempo limite: herde a classe WebClient original e substitua o getter webrequest para definir seu próprio tempo limite, como no exemplo a seguir.

MyWebClient era uma classe privada no meu caso:

private class MyWebClient : WebClient
{
    protected override WebRequest GetWebRequest(Uri uri)
    {
        WebRequest w = base.GetWebRequest(uri);
        w.Timeout = 20 * 60 * 1000;
        return w;
    }
}
kisp
fonte
5
qual é o tempo limite padrão ??
knocte
23
O tempo limite padrão é de 100 segundos. Embora pareça funcionar por 30 segundos.
Carter Medlin
3
Um pouco mais fácil de definir o Timeout com um Timespan TimeSpan.FromSeconds (20). Milissegundos ... ótima solução!
Webwires
18
@webwires Deve-se usar .TotalMillisecondse não .Milliseconds!
Alexander Galkin
80
O nome da classe deve ser: PatientWebClient;)
Jan Willem B
27

A primeira solução não funcionou para mim, mas aqui está um código que funcionou para mim.

    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
fonte
21

Você precisa usar HttpWebRequeste não WebClientcomo não pode definir o tempo limite WebClientsem estendê-lo (mesmo que ele use o HttpWebRequest). Usar o HttpWebRequestlugar permitirá que você defina o tempo limite.

Fenton
fonte
Isso não é verdade ... você pode ver acima que ainda pode usar o WebClient, embora uma implementação personalizada que substitua o WebRequest para definir o tempo limite.
precisa saber é o seguinte
7
"System.Net.HttpWebRequest.HttpWebRequest () 'é obsoleto:' Esta API oferece suporte à infraestrutura .NET Framework e não se destina a ser usado diretamente em seu código"
usefulBee
3
@usefulBee - porque você não deve chamar esse construtor: "Do not use the HttpWebRequest constructor. Use the WebRequest.Create method to initialize new HttpWebRequest objects."de msdn.microsoft.com/en-us/library/… . Ver também stackoverflow.com/questions/400565/...
ToolmakerSteve
Só para esclarecer: Enquanto este construtor específico deve ser evitado (não é mais parte de versões mais recentes do .NET de qualquer maneira), é perfeitamente possível usar a Timeoutpropriedade de HttpWebRequest. Está em milissegundos.
Marcel
10

Não foi possível obter o código w.Timeout quando o cabo de rede foi puxado, ele não estava atingindo o tempo limite, passou a usar o HttpWebRequest e faz o trabalho agora.

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
fonte
1
Esta resposta funciona muito bem, mas para qualquer pessoa interessada, se você usar em var wresp = await request.GetResponseAsync();vez de var wresp = (HttpWebResponse)request.GetResponse();, terá um tempo limite enorme novamente
andrewjboyd
andrewjboyd: você sabe por que GetResponseAsync () não funciona?
Osexpert
9

Para completar, aqui está a solução do kisp portada para o VB (não é possível adicionar código a um comentário)

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
fonte
7

Como Sohnee diz, use System.Net.HttpWebRequeste defina a Timeoutpropriedade em vez de usar System.Net.WebClient.

No entanto, você não pode definir um valor de tempo limite infinito (não é suportado e a tentativa de fazer isso gerará um ArgumentOutOfRangeException).

Eu recomendo primeiro executar uma solicitação HTTP HEAD e examinar o Content-Lengthvalor do cabeçalho retornado para determinar o número de bytes no arquivo que você está baixando e, em seguida, definir o valor do tempo limite de acordo com a GETsolicitação subsequente ou simplesmente especificar um valor de tempo limite muito longo que você deseja nunca espere exceder.

dariom
fonte
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
fonte
5

Para quem precisa de um WebClient com um tempo limite que funcione para métodos assíncronos / tarefas , as soluções sugeridas não funcionarão. Aqui está o que funciona:

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

Eu escrevi sobre a solução completa aqui

Alex
fonte
4

Uso:

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

Classe:

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

Mas eu recomendo uma solução mais moderna:

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

Ele lançará um OperationCanceledExceptionapós um tempo limite.

Konstantin S.
fonte
Não funcionavam, o método assíncrono ainda funciona indefinidamente
Alex
Talvez o problema seja diferente e você precise usar o ConfigureAwait (false)?
Konstantin S.
-1

Em alguns casos, é necessário adicionar o agente do usuário aos cabeçalhos:

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

Esta foi a solução para o meu caso.

Crédito:

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

antoine
fonte