Як змінити час очікування на об’єкт .NET WebClient


230

Я намагаюся завантажити дані клієнта на свою локальну машину (програмно), і їх веб-сервер дуже-дуже повільний, що спричиняє тайм-аут у мого WebClientоб’єкта.

Ось мій код:

WebClient webClient = new WebClient();

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

Чи є спосіб встановити нескінченний тайм-аут на цьому об’єкті? Або якщо ні, не може хтось допомогти мені прикладом альтернативного способу зробити це?

URL-адреса працює у веб-переглядачі - на це потрібно лише 3 хвилини.

Відповіді:


378

Ви можете продовжити тайм-аут: успадкуйте оригінальний клас WebClient та замініть отримувач веб-запитів, щоб встановити свій власний тайм-аут, як у наступному прикладі.

У моєму випадку MyWebClient був приватним класом:

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

5
який тайм-аут за замовчуванням ??
knocte

23
Тимчасовий час очікування становить 100 секунд. Хоча, здається, працює протягом 30 секунд.
Картер Медлін

3
Трохи простіше встановити тайм-аут за допомогою Timespan TimeSpan.FromSeconds (20) .Milliseconds ... Хоча чудове рішення!
webwires

18
@webwires Треба використовувати, .TotalMillisecondsа ні .Milliseconds!
Олександр Галкін

80
Назва класу повинна бути: PatientWebClient;)
Ян Віллем B

27

Перше рішення не працювало для мене, але ось якийсь код, який працював для мене.

    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

Вам потрібно використовувати, HttpWebRequestа не WebClientяк ви не можете встановити тайм-аут, WebClientне продовжуючи його (навіть якщо він використовує HttpWebRequest). Використання HttpWebRequestнатомість дозволить вам встановити час очікування.


Це неправда ... Ви можете бачити вище, що ви все ще можете використовувати WebClient, хоч і спеціальну реалізацію, що перекриває WebRequest для встановлення тайм-ауту.
DomenicDatti

7
«System.Net.HttpWebRequest.HttpWebRequest () є застарілим:" Цей API підтримує інфраструктуру .NET Framework і не призначений для використання безпосередньо з коду »
usefulBee

3
@usefulBee - тому що ви не повинні викликати цей конструктор: "Do not use the HttpWebRequest constructor. Use the WebRequest.Create method to initialize new HttpWebRequest objects."від msdn.microsoft.com/en-us/library / ... . Також дивіться stackoverflow.com/questions/400565/…
ToolmakerSteve

Тільки для уточнення: Хоча цього конкретного конструктора слід уникати (все одно це більше не частина нової версії .NET), цілком чудово використовувати Timeoutвластивість HttpWebRequest. Це в мілісекундах.
Марсель

10

Не вдалося змусити код w.Timeout працювати, коли витягнути мережевий кабель, він просто не минув, перейшов до використання HttpWebRequest і виконує завдання зараз.

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
Ця відповідь чудово працює, але для всіх, хто зацікавиться, якщо ви будете використовувати var wresp = await request.GetResponseAsync();замість var, wresp = (HttpWebResponse)request.GetResponse();ви знову отримаєте масовий тайм-аут
andrewjboyd

andrewjboyd: чи знаєте ви, чому GetResponseAsync () не працює?
osexpert

9

Для повноти ось рішення kisp, перенесене на VB (не можна додати код до коментаря)

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

Як каже Соньє, використовуючи System.Net.HttpWebRequestта встановлюючи Timeoutвластивість замість використання System.Net.WebClient.

Однак ви не можете встановити нескінченне значення тайм-ауту (воно не підтримується, і спроби зробити це будуть кидати ArgumentOutOfRangeException).

Я рекомендую спочатку виконати запит HEAD HTTP і вивчити Content-Lengthзначення заголовка, що повертається, щоб визначити кількість байтів у завантажуваному файлі, а потім встановити значення тайм-ауту відповідно для наступного GETзапиту або просто вказати дуже довге значення часу очікування, яке ви б ніколи не розраховуйте перевищити.


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

Для тих, хто потребує WebClient з таймаутом, який працює для методів асинхронізації / завдання , запропоновані рішення не працюватимуть. Ось що працює:

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

Я бгігував про повне вирішення тут


4

Використання:

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

Клас:

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

Але я рекомендую більш сучасне рішення:

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

Він буде кинутий OperationCanceledExceptionпісля таймауту.


Не працював, метод асинхронізації все ще працює нескінченно
Алекс

Можливо, проблема в іншому, і вам потрібно використовувати ConfigureAwait (false)?
Костянтин С.

-1

У деяких випадках необхідно додати агент користувача до заголовків:

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

Це було рішенням моєї справи.

Кредит:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.