Comment modifier le délai d'attente sur un objet .NET WebClient

230

J'essaie de télécharger les données d'un client sur ma machine locale (par programme) et leur serveur Web est très, très lent, ce qui provoque un timeout dans mon WebClientobjet.

Voici mon code:

WebClient webClient = new WebClient();

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

Existe-t-il un moyen de définir une temporisation infinie sur cet objet? Ou sinon, quelqu'un peut-il m'aider avec un exemple sur une autre façon de le faire?

L'URL fonctionne bien dans un navigateur - il ne faut que 3 minutes environ pour s'afficher.

Ryall
la source

Réponses:

378

Vous pouvez étendre le délai d'expiration: héritez de la classe WebClient d'origine et remplacez le getter Webrequest pour définir votre propre délai d'expiration, comme dans l'exemple suivant.

MyWebClient était une classe privée dans mon cas:

private class MyWebClient : WebClient
{
    protected override WebRequest GetWebRequest(Uri uri)
    {
        WebRequest w = base.GetWebRequest(uri);
        w.Timeout = 20 * 60 * 1000;
        return w;
    }
}
kisp
la source
5
quel est le délai d'expiration par défaut ??
knocte
23
Le délai d'expiration par défaut est de 100 secondes. Bien qu'il semble fonctionner pendant 30 secondes.
Carter Medlin
3
Un peu plus facile à régler le Timeout avec un TimeSpan TimeSpan.FromSeconds (20) .Milliseconds ... excellente solution cependant!
webwires
18
@webwires On devrait utiliser .TotalMillisecondset non .Milliseconds!
Alexander Galkin
80
Le nom de la classe doit être: PatientWebClient;)
Jan Willem B
27

La première solution n'a pas fonctionné pour moi, mais voici un code qui a fonctionné pour moi.

    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
la source
21

Vous devez utiliser HttpWebRequestplutôt que WebClientcar vous ne pouvez pas définir le délai d'attente WebClientsans le prolonger (même s'il utilise le HttpWebRequest). Utiliser à la HttpWebRequestplace vous permettra de définir le délai d'expiration.

Fenton
la source
Ce n'est pas vrai ... vous pouvez voir ci-dessus que vous pouvez toujours utiliser WebClient, bien qu'une implémentation personnalisée qui remplace la WebRequest pour définir le délai d'expiration.
DomenicDatti
7
"System.Net.HttpWebRequest.HttpWebRequest () 'est obsolète:' Cette API prend en charge l'infrastructure .NET Framework et n'est pas destinée à être utilisée directement à partir de votre code"
utileBee
3
@usefulBee - parce que vous ne devez pas appeler ce constructeur: "Do not use the HttpWebRequest constructor. Use the WebRequest.Create method to initialize new HttpWebRequest objects."depuis msdn.microsoft.com/en-us/library/… . Voir également stackoverflow.com/questions/400565/…
ToolmakerSteve
Juste pour clarifier: alors que ce constructeur spécifique doit être évité (il ne fait plus partie des versions plus récentes de .NET de toute façon), il est tout à fait correct d'utiliser la Timeoutpropriété de HttpWebRequest. C'est en millisecondes.
Marcel
10

Impossible de faire fonctionner le code w.Timeout lorsque le câble réseau a été retiré, il n'existait tout simplement pas, il est passé à l'utilisation de HttpWebRequest et fait le travail maintenant.

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
la source
1
Cette réponse fonctionne très bien, mais pour toute personne intéressée, si vous utilisez var wresp = await request.GetResponseAsync();au lieu de var, wresp = (HttpWebResponse)request.GetResponse();vous obtiendrez à nouveau un délai d'attente massif
andrewjboyd
andrewjboyd: savez-vous pourquoi GetResponseAsync () ne fonctionne pas?
osexpert
9

Pour être complet, voici la solution de kisp portée sur VB (impossible d'ajouter du code à un commentaire)

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
la source
7

Comme le dit Sohnee, utiliser System.Net.HttpWebRequestet définir la Timeoutpropriété au lieu d'utiliser System.Net.WebClient.

Vous ne pouvez cependant pas définir une valeur de délai infini (ce n'est pas pris en charge et tenter de le faire lancera un ArgumentOutOfRangeException).

Je recommande d'abord d'effectuer une demande HEAD HTTP et d'examiner la Content-Lengthvaleur d'en-tête retournée pour déterminer le nombre d'octets dans le fichier que vous téléchargez, puis de définir la valeur de délai en conséquence pour la GETdemande suivante ou simplement de spécifier une très longue valeur de délai que vous souhaitez ne vous attendez jamais à dépasser.

dariom
la source
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
la source
5

Pour toute personne qui a besoin d'un WebClient avec un délai d'attente qui fonctionne pour les méthodes asynchrones / tâches , les solutions suggérées ne fonctionneront pas. Voici ce qui fonctionne:

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

J'ai blogué sur la solution complète ici

Alex
la source
4

Usage:

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

Mais je recommande une solution plus moderne:

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

Il lancera un OperationCanceledExceptionaprès un temps mort.

Konstantin S.
la source
N'a pas fonctionné, la méthode async fonctionne toujours indéfiniment
Alex
Peut-être que le problème est différent et que vous devez utiliser ConfigureAwait (false)?
Konstantin S.
-1

Dans certains cas, il est nécessaire d'ajouter un agent utilisateur aux en-têtes:

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

C'était la solution à mon cas.

Crédit:

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

antoine
la source