Cómo cambiar el tiempo de espera en un objeto .NET WebClient

230

Estoy tratando de descargar los datos de un cliente a mi máquina local (programáticamente) y su servidor web es muy, muy lento, lo que está causando un tiempo de espera en mi WebClientobjeto.

Aquí está mi código:

WebClient webClient = new WebClient();

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

¿Hay alguna manera de establecer un tiempo de espera infinito en este objeto? O si no, ¿alguien puede ayudarme con un ejemplo en una forma alternativa de hacer esto?

La URL funciona bien en un navegador: solo tarda unos 3 minutos en mostrarse.

Ryall
fuente

Respuestas:

378

Puede extender el tiempo de espera: herede la clase WebClient original y anule el getbrequest weter para establecer su propio tiempo de espera, como en el siguiente ejemplo.

MyWebClient era una clase privada en mi caso:

private class MyWebClient : WebClient
{
    protected override WebRequest GetWebRequest(Uri uri)
    {
        WebRequest w = base.GetWebRequest(uri);
        w.Timeout = 20 * 60 * 1000;
        return w;
    }
}
kisp
fuente
55
¿Cuál es el tiempo de espera predeterminado?
Knocte
23
El tiempo de espera predeterminado es de 100 segundos. Aunque parece correr por 30 segundos.
Carter Medlin
3
Es un poco más fácil configurar el tiempo de espera con un intervalo de tiempo TimeSpan.FromSeconds (20).
webwires
18
@webwires ¡Uno debe usar .TotalMillisecondsy no .Milliseconds!
Alexander Galkin
80
El nombre de la clase debe ser: PatientWebClient;)
Jan Willem B
27

La primera solución no funcionó para mí, pero aquí hay un código que funcionó para mí.

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

Debe usarlo en HttpWebRequestlugar de hacerlo, WebClientya que no puede establecer el tiempo de espera WebClientsin extenderlo (aunque use el HttpWebRequest). El uso de en su HttpWebRequestlugar le permitirá establecer el tiempo de espera.

Fenton
fuente
Esto no es cierto ... puede ver arriba que todavía puede usar WebClient, aunque una implementación personalizada que anula la WebRequest para establecer el tiempo de espera.
DomenicDatti
77
"System.Net.HttpWebRequest.HttpWebRequest () 'está obsoleto:' Esta API es compatible con la infraestructura de .NET Framework y no está diseñada para ser utilizada directamente desde su código"
útilBee
3
@usefulBee - porque no deberías llamar a ese constructor: "Do not use the HttpWebRequest constructor. Use the WebRequest.Create method to initialize new HttpWebRequest objects."desde msdn.microsoft.com/en-us/library/… . Ver también stackoverflow.com/questions/400565/…
ToolmakerSteve
Solo para aclarar: si bien este constructor específico debe evitarse (de todos modos, ya no forma parte de las versiones más recientes de .NET), está perfectamente bien usar la Timeoutpropiedad de HttpWebRequest. Está en milisegundos.
Marcel
10

No se pudo hacer que el código w.Timeout funcionara cuando se desconectó el cable de red, simplemente no se estaba agotando el tiempo, se movió a usar HttpWebRequest y ahora hace el trabajo.

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);
}
lesullet
fuente
1
Esta respuesta funciona muy bien, pero para cualquier persona interesada, si usa en var wresp = await request.GetResponseAsync();lugar de var wresp = (HttpWebResponse)request.GetResponse();, obtendrá un tiempo de espera masivo nuevamente
andrewjboyd
andrewjboyd: ¿sabes por qué GetResponseAsync () no funciona?
osexpert
9

Para completar, aquí está la solución de kisp portada a VB (no se puede agregar código a un comentario)

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

Como dice Sohnee, usar System.Net.HttpWebRequesty establecer la Timeoutpropiedad en lugar de usar System.Net.WebClient.

Sin embargo, no puede establecer un valor de tiempo de espera infinito (no es compatible e intentar hacerlo arrojará un ArgumentOutOfRangeException)

Recomiendo primero realizar una solicitud HEAD HTTP y examinar el Content-Lengthvalor del encabezado devuelto para determinar el número de bytes en el archivo que está descargando y luego configurar el valor de tiempo de espera correspondiente para la GETsolicitud posterior o simplemente especificar un valor de tiempo de espera muy largo Nunca esperes superarlo.

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

Para cualquiera que necesite un WebClient con un tiempo de espera que funcione para métodos asincrónicos / de tareas , las soluciones sugeridas no funcionarán. Esto es lo 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;
    }
}

Escribí en un blog sobre la solución completa aquí

Alex
fuente
4

Uso:

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

Clase:

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

Pero recomiendo una solución más 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);
}

Lanzará un OperationCanceledExceptiondespués de un tiempo de espera.

Konstantin S.
fuente
No funcionó, el método asíncrono aún funciona indefinidamente
Alex
¿Quizás el problema es diferente y necesita usar ConfigureAwait (falso)?
Konstantin S.
-1

En algunos casos es necesario agregar agente de usuario a los encabezados:

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 fue la solución a mi caso.

Crédito:

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

antoine
fuente