简体   繁体   English

尝试使用WebClient.DownloadFile()或WebRequest下载文件时无法实现超时

[英]Can't implement timeout when trying to download a file using WebClient.DownloadFile() or WebRequest

I'm trying to implement some functionality that downloads a file from a URL. 我正在尝试实现一些从URL下载文件的功能。 However, if the file is taking longer than 30 seconds, I'd like to cancel the download, or have it time out. 但是,如果文件花费的时间超过30秒,我想取消下载或使其超时。

I've tried overriding the WebClient class to implement a timeout, but no matter what value I set the timeout to, it never times out! 我尝试重写WebClient类以实现超时,但是无论我将超时设置为什么值,它都永远不会超时! Here is the code I've tried, found in another stackoverflow answer : 这是我尝试过的代码, 可在另一个stackoverflow答案中找到

using System;
using System.Net;

public class WebDownload : WebClient
{
    /// <summary>
    /// Time in milliseconds
    /// </summary>
    public int Timeout { get; set; }

    public WebDownload() : this(60000) { }

    public WebDownload(int timeout)
    {
        this.Timeout = timeout;
    }

    protected override WebRequest GetWebRequest(Uri address)
    {
        var request = base.GetWebRequest(address);
        if (request != null)
        {
            request.Timeout = this.Timeout;
        }
        return request;
    }
}

Then, called using: 然后,使用调用:

 WebDownload webClient = new WebDownload(20000);
 try 
 {               
      webClient.DownloadFile(url, tmpFile);
 }
 catch {
     //throw error
 }

I've also tried using the WebRequest method to download the file, and using the Timeout and ReadWriteTimeout properties, but no dice. 我也尝试过使用WebRequest方法下载文件,并使用Timeout和ReadWriteTimeout属性,但是没有骰子。 This has to be a pretty common use case. 必须是一个非常常见的用例。 Any help is appreciated. 任何帮助表示赞赏。 Thanks! 谢谢!

How about creating an extension method? 如何创建扩展方法?

WebClient wc = new WebClient();
wc.DownloadFileWithTimeout(url, filename, 20000);

public static class SOExtensions
{
    public static void DownloadFileWithTimeout(this WebClient wc, string url, string file, int timeout)
    {
        var tcs = new TaskCompletionSource<bool>();

        var bgTask = Task.Factory.StartNew(() =>
        {
            wc.DownloadFileTaskAsync(url, file).Wait();
            tcs.TrySetResult(true);
        });


        if (!bgTask.Wait(timeout))
        {
            wc.CancelAsync();
            throw new TimeoutException("Timed out while downloading \"" + url + "\"");
        }
    }
}

The timeout you implemented concerns getting Response, but not ResponseStream which contains all the data and takes usually more time to achieve. 您实现的超时关系到获取响应,而不是获取包含所有数据并且通常需要更多时间来实现的ResponseStream。 For example getting response usually takes below 1 second, but downloading content of a web page could take few seconds. 例如,获取响应通常需要不到1秒,但是下载网页内容可能需要几秒钟。

As for downloading files, you can use HttpWebRequest to get HttpWebResponse and from it use method GetResponseStream() to get the stream to the data. 至于下载文件,您可以使用HttpWebRequest获取HttpWebResponse然后从中使用方法GetResponseStream()获取数据流。

This will be helpful: Encoding trouble with HttpWebResponse 这将是有帮助的: HttpWebResponse的编码问题

Especially the part where byte[] buffer is used to get parts of data instead of StreamReader ReadToEnd() method. 特别是使用byte[] buffer而不是StreamReader ReadToEnd()方法获取数据部分的部分。 While downloading parts of data to buffer you may check current DateTime against the timeout DateTime and thus allow cancelling the download after it. 在下载部分数据以缓冲时,您可以对照超时日期时间检查当前日期时间,从而允许在其之后取消下载。

Edit: a useful piece of code 编辑:一段有用的代码

private byte[] DownloadFile( string uri, int requestTimeout, int downloadTimeout, out bool isTimeout, out int bytesDownloaded )
{
    HttpWebRequest request = WebRequest.Create( uri ) as HttpWebRequest;
    request.Timeout = requestTimeout;
    HttpWebResponse response = null;
    Stream responseStream = null;
    MemoryStream downloadedData = null;

    byte[] result = null;
    bytesDownloaded = 0;

    isTimeout = false;
    try
    {
        // Get response
        response = request.GetResponse() as HttpWebResponse;
        byte[] buffer = new byte[ 16384 ];

        // Create buffer for downloaded data
        downloadedData = new MemoryStream();

        // Set the timeout
        DateTime timeout = DateTime.Now.Add( new TimeSpan( 0, 0, 0, 0, downloadTimeout ) );

        // Read parts of the stream
        responseStream = response.GetResponseStream();
        int bytesRead = 0;
        DateTime now = DateTime.Now;
        while ( (bytesRead = responseStream.Read( buffer, 0, buffer.Length )) > 0 && DateTime.Now < timeout )
        {
            downloadedData.Write( buffer, 0, bytesRead );
            now = DateTime.Now;
            bytesDownloaded += bytesRead;
        }

        // Notify if timeout occured (could've been written better)
        if ( DateTime.Now >= timeout )
        {
            isTimeout = true;
        }
    }
    catch ( WebException ex )
    {
        // Grab timeout exception
        if ( ex.Status == WebExceptionStatus.Timeout )
        {
            isTimeout = true;
        }
    }
    finally
    {
        // Close the stream
        if ( responseStream != null )
        {
            responseStream.Close();
        }
    }

    if ( downloadedData != null )
    {
        result = downloadedData.GetBuffer();
        downloadedData.Close();
    }

    return result;
}

Usage 用法

private void button1_Click( object sender, EventArgs e )
{
    bool isTimeout;
    int bytesDownloaded;
    byte[] data = DownloadFile( something, 1000,500, out isTimeout, out bytesDownloaded );

    MessageBox.Show( "Downloaded " + bytesDownloaded.ToString() + " bytes, Timeout = " + isTimeout.ToString() );
}

This code is still vulnerable for other exceptions you may encounter, bear that in mind. 请记住,此代码仍然容易受到您可能遇到的其他异常的影响。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM