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

时间:2013-10-18 21:13:06

标签: c# .net asp.net-mvc-4

我正在尝试实现一些从URL下载文件的功能。但是,如果文件花费的时间超过30秒,我想取消下载,或者让它超时。

我已经尝试重写WebClient类来实现超时,但无论我将超时设置为什么值,它都不会超时!以下是我尝试过的代码,found in another stackoverflow answer

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

然后,使用:

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

我也尝试使用WebRequest方法下载文件,并使用Timeout和ReadWriteTimeout属性,但没有骰子。这个是一个非常常见的用例。任何帮助表示赞赏。谢谢!

2 个答案:

答案 0 :(得分:1)

如何创建扩展方法?

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 + "\"");
        }
    }
}

答案 1 :(得分:0)

您实施的超时涉及获取响应,但不包含包含所有数据的ResponseStream,并且通常需要更多时间来实现。例如,获得响应通常需要1秒以下,但下载网页内容可能需要几秒钟。

至于下载文件,您可以使用HttpWebRequest获取HttpWebResponse,然后使用方法GetResponseStream()获取数据流。

这会有所帮助: Encoding trouble with HttpWebResponse

尤其是byte[] buffer用于获取部分数据而非StreamReader ReadToEnd()方法的部分。下载部分数据到缓冲区时,可以根据超时DateTime检查当前的DateTime,从而允许在它之后取消下载。

编辑一段有用的代码

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

<强>用法

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

此代码仍然容易受到您可能遇到的其他异常的影响,请记住这一点。