Cara mengubah batas waktu pada objek .NET WebClient

230

Saya mencoba mengunduh data klien ke mesin lokal saya (secara terprogram) dan server web mereka sangat, sangat lambat yang menyebabkan batas waktu pada WebClientobjek saya .

Ini kode saya:

WebClient webClient = new WebClient();

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

Apakah ada cara untuk menetapkan batas waktu tak terbatas pada objek ini? Atau jika tidak ada yang bisa membantu saya dengan contoh tentang cara alternatif untuk melakukan ini?

URL berfungsi dengan baik di browser - hanya membutuhkan waktu sekitar 3 menit untuk ditampilkan.

Ryall
sumber

Jawaban:

378

Anda dapat memperpanjang batas waktu: mewarisi kelas WebClient asli dan menimpa pengambil permintaan web untuk menetapkan batas waktu Anda sendiri, seperti dalam contoh berikut.

MyWebClient adalah kelas privat dalam kasus saya:

private class MyWebClient : WebClient
{
    protected override WebRequest GetWebRequest(Uri uri)
    {
        WebRequest w = base.GetWebRequest(uri);
        w.Timeout = 20 * 60 * 1000;
        return w;
    }
}
kisp
sumber
5
apa batas waktu default ??
knocte
23
Batas waktu default adalah 100 detik. Meski sepertinya berjalan selama 30 detik.
Carter Medlin
3
Sedikit lebih mudah untuk mengatur Timeout dengan Timespan TimeSpan.FromSeconds (20) .Milliseconds ... solusi hebat!
webwires
18
@webwires One harus digunakan .TotalMillisecondsdan tidak .Milliseconds!
Alexander Galkin
80
Nama kelas harus: PatientWebClient;)
Jan Willem B
27

Solusi pertama tidak bekerja untuk saya tetapi di sini ada beberapa kode yang berhasil bagi saya.

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

Anda perlu menggunakan HttpWebRequestdaripada WebClientkarena Anda tidak dapat mengaktifkan batas waktu WebClienttanpa memperpanjangnya (meskipun menggunakan HttpWebRequest). Menggunakan HttpWebRequestmalah akan memungkinkan Anda untuk mengatur batas waktu.

Fenton
sumber
Ini tidak benar ... Anda dapat melihat di atas bahwa Anda masih dapat menggunakan WebClient, meskipun implementasi kustom yang mengesampingkan WebRequest untuk menetapkan batas waktu.
DomenicDatti
7
"System.Net.HttpWebRequest.HttpWebRequest () 'sudah usang:' API ini mendukung infrastruktur .NET Framework dan tidak dimaksudkan untuk digunakan langsung dari kode Anda"
usefulBee
3
@usefulBee - karena Anda tidak boleh memanggil konstruktor itu: "Do not use the HttpWebRequest constructor. Use the WebRequest.Create method to initialize new HttpWebRequest objects."dari msdn.microsoft.com/en-us/library/… . Juga lihat stackoverflow.com/questions/400565/…
ToolmakerSteve
Hanya untuk memperjelas: Walaupun konstruktor khusus ini harus dihindari (toh tidak ada lagi bagian dari versi .NET yang lebih baru), tidak apa-apa untuk menggunakan Timeoutproperti HttpWebRequest. Ini dalam milidetik.
Marcel
10

Tidak bisa mendapatkan kode w.Timeout bekerja ketika menarik keluar kabel jaringan, itu hanya tidak waktunya, pindah ke menggunakan HttpWebRequest dan melakukan pekerjaan sekarang.

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
sumber
1
Jawaban ini berfungsi dengan baik, tetapi bagi siapa pun yang tertarik, jika Anda menggunakan var wresp = await request.GetResponseAsync();alih-alih var, wresp = (HttpWebResponse)request.GetResponse();Anda akan mendapatkan batas waktu besar lagi
andrewjboyd
andrewjboyd: apakah Anda tahu mengapa GetResponseAsync () tidak berfungsi?
osexpert
9

Untuk kelengkapan, inilah solusi kisp yang diport ke VB (tidak dapat menambahkan kode ke komentar)

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

Seperti kata Sohnee, menggunakan System.Net.HttpWebRequestdan mengatur Timeoutproperti alih-alih menggunakan System.Net.WebClient.

Namun Anda tidak dapat menetapkan nilai batas waktu tak terbatas (tidak didukung dan mencoba melakukannya akan melempar ArgumentOutOfRangeException).

Saya akan merekomendasikan terlebih dahulu melakukan permintaan HTTP HEAD dan memeriksa nilai Content-Lengthheader yang dikembalikan untuk menentukan jumlah byte dalam file yang Anda unduh dan kemudian mengatur nilai batas waktu sesuai untuk GETpermintaan berikutnya atau hanya menentukan nilai batas waktu yang sangat lama yang Anda inginkan. jangan pernah berharap melebihi.

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

Bagi siapa pun yang membutuhkan WebClient dengan batas waktu yang berfungsi untuk metode tugas / sinkronisasi , solusi yang disarankan tidak akan berfungsi. Inilah yang berhasil:

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

Saya menulis blog tentang penyelesaian penuh di sini

Alex
sumber
4

Pemakaian:

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

Kelas:

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

Tetapi saya merekomendasikan solusi yang lebih modern:

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

Ini akan melempar OperationCanceledExceptionsetelah batas waktu.

Konstantin S.
sumber
Tidak berhasil, metode async masih bekerja tanpa batas
Alex
Mungkin masalahnya berbeda dan Anda perlu menggunakan ConfigureAwait (false)?
Konstantin S.
-1

Dalam beberapa kasus, perlu menambahkan agen pengguna ke tajuk:

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

Ini adalah solusi untuk kasus saya.

Kredit:

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

antoine
sumber