Come modificare il timeout su un oggetto .NET WebClient


230

Sto cercando di scaricare i dati di un client sul mio computer locale (a livello di codice) e il loro server web è molto, molto lento, il che sta causando un timeout nel mio WebClientoggetto.

Ecco il mio codice:

WebClient webClient = new WebClient();

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

C'è un modo per impostare un timeout infinito su questo oggetto? O se no qualcuno può aiutarmi con un esempio su un modo alternativo per farlo?

L'URL funziona bene in un browser: bastano solo circa 3 minuti per mostrarlo.

Risposte:


378

È possibile estendere il timeout: ereditare la classe WebClient originale e sovrascrivere il getter di richiesta web per impostare il proprio timeout, come nell'esempio seguente.

MyWebClient era una classe privata nel mio caso:

private class MyWebClient : WebClient
{
    protected override WebRequest GetWebRequest(Uri uri)
    {
        WebRequest w = base.GetWebRequest(uri);
        w.Timeout = 20 * 60 * 1000;
        return w;
    }
}

5
qual è il timeout predefinito ??
Knocte,

23
Il timeout predefinito è 100 secondi. Anche se sembra funzionare per 30 secondi.
Carter Medlin,

3
Un po 'più facile impostare il Timeout con un TimeSpan.FromSeconds (20) .Millisecondi ... ottima soluzione però!
webwires

18
@webwires Uno dovrebbe usare .TotalMillisecondse non .Milliseconds!
Alexander Galkin,

80
Il nome della classe dovrebbe essere: PatientWebClient;)
Jan Willem B

27

La prima soluzione non ha funzionato per me, ma ecco un codice che ha funzionato per me.

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

21

È necessario utilizzare HttpWebRequestpiuttosto che WebClientnon è possibile impostare il timeout WebClientsenza estenderlo (anche se utilizza il HttpWebRequest). L'uso di HttpWebRequestinvece ti permetterà di impostare il timeout.


Questo non è vero ... puoi vedere sopra che puoi ancora usare WebClient, anche se un'implementazione personalizzata che sovrascrive il WebRequest per impostare il timeout.
DomenicDatti,

7
"System.Net.HttpWebRequest.HttpWebRequest () 'è obsoleto:' Questa API supporta l'infrastruttura .NET Framework e non è pensata per essere utilizzata direttamente dal tuo codice"
utilBee,

3
@usefulBee - perché non dovresti chiamare quel costruttore: "Do not use the HttpWebRequest constructor. Use the WebRequest.Create method to initialize new HttpWebRequest objects."da msdn.microsoft.com/en-us/library/… . Vedere anche stackoverflow.com/questions/400565/...
ToolmakerSteve

Solo per chiarire: mentre questo costruttore specifico dovrebbe essere evitato (non fa più parte delle versioni .NET più recenti), è perfettamente bene usare la Timeoutproprietà di HttpWebRequest. È in millisecondi.
Marcel,

10

Non è stato possibile far funzionare il codice w.Timeout quando è stato estratto il cavo di rete, semplicemente non è scaduto, è passato a utilizzare HttpWebRequest e fa il lavoro ora.

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

1
Questa risposta funziona benissimo, ma per chiunque sia interessato, se usi al var wresp = await request.GetResponseAsync();posto di var wresp = (HttpWebResponse)request.GetResponse();otterrai di nuovo un enorme timeout
andrewjboyd

andrewjboyd: sai perché GetResponseAsync () non funziona?
osexpert,

9

Per completezza, ecco la soluzione di Kisp trasferita su VB (impossibile aggiungere codice a un commento)

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

7

Come dice Sohnee, usare System.Net.HttpWebRequeste impostare la Timeoutproprietà invece di usare System.Net.WebClient.

Non è tuttavia possibile impostare un valore di timeout infinito (non è supportato e tentare di farlo genererà un ArgumentOutOfRangeException).

Ti consiglio di eseguire prima una richiesta HEAD HTTP e di esaminare il Content-Lengthvalore dell'intestazione restituito per determinare il numero di byte nel file che stai scaricando e quindi impostare il valore di timeout di conseguenza per la GETrichiesta successiva o semplicemente specificare un valore di timeout molto lungo che mai aspettarsi di superare.


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

5

Per chiunque abbia bisogno di un WebClient con un timeout che funzioni con metodi asincroni / attività , le soluzioni suggerite non funzioneranno. Ecco cosa funziona:

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

Ho scritto un blog sull'intera soluzione qui


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

Ma raccomando una soluzione più 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);
}

Lancerà un OperationCanceledExceptiontimeout.


Non ha funzionato, il metodo asincrono funziona ancora indefinitamente
Alex

Forse il problema è diverso e devi usare ConfigureAwait (false)?
Konstantin S.

-1

In alcuni casi è necessario aggiungere l'agente utente alle intestazioni:

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

Questa era la soluzione al mio caso.

Credito:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.