.Net DownloadFileTaskAsync强大的WPF代码

时间:2017-02-23 19:14:22

标签: c# wpf asynchronous webclient

当网络连接丢失3分钟或更长时间时,下面的WPF代码将永久挂起。当连接恢复时,它既不会抛出也不会继续下载或超时。如果网络连接在较短的时间内丢失(例如半分钟),则在连接恢复后会丢失。如何才能使网络中断更加强大?

android:layoutDirection="rtl"

更新

当前解决方案基于重新启动using System; using System.Net; using System.Net.NetworkInformation; using System.Windows; namespace WebClientAsync { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); NetworkChange.NetworkAvailabilityChanged += (sender, e) => Dispatcher.Invoke(delegate() { this.Title = "Network is " + (e.IsAvailable ? " available" : "down"); }); } const string SRC = "http://ovh.net/files/10Mio.dat"; const string TARGET = @"d:\stuff\10Mio.dat"; private async void btnDownload_Click(object sender, RoutedEventArgs e) { btnDownload.IsEnabled = false; btnDownload.Content = "Downloading " + SRC; try { using (var wcl = new WebClient()) { wcl.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials; await wcl.DownloadFileTaskAsync(new Uri(SRC), TARGET); btnDownload.Content = "Downloaded"; } } catch (Exception ex) { btnDownload.Content = ex.Message + Environment.NewLine + ((ex.InnerException != null) ? ex.InnerException.Message : String.Empty); } btnDownload.IsEnabled = true; } } } 中的Timer,因此只有在超时内没有发生DownloadProgressChanged事件时才会触发计时器。看起来像一个丑陋的黑客,仍在寻找更好的解决方案。

DownloadProgressChangedEventHandler

2 个答案:

答案 0 :(得分:9)

您需要为该下载实现正确的超时。但是,您不需要使用计时器,只需使用Task.DelayTask.WaitAny.例如:

static async Task DownloadFile(string url, string output, TimeSpan timeout) {            
    using (var wcl = new WebClient())
    {
        wcl.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;                                                
        var download = wcl.DownloadFileTaskAsync(url, output);
        // await two tasks - download and delay, whichever completes first
        await Task.WhenAny(Task.Delay(timeout), download);
        var exception = download.Exception; // need to observe exception, if any
        bool cancelled = !download.IsCompleted && exception == null;

        // download is not completed yet, nor it is failed - cancel
        if (cancelled) {
            wcl.CancelAsync();
        }

        if (cancelled || exception != null) {
            // delete partially downloaded file if any (note - need to do with retry, might not work with a first try, because CancelAsync is not immediate)
            int fails = 0;
            while (true) {
                try {
                    File.Delete(output);
                    break;
                }
                catch {
                    fails++;
                    if (fails >= 10)
                        break;

                    await Task.Delay(1000);
                }
            }
        }
        if (exception != null) {
            throw new Exception("Failed to download file", exception);
        }
        if (cancelled) {
            throw new Exception($"Failed to download file (timeout reached: {timeout})");
        }
    }
}

用法:

const string SRC = "http://ovh.net/files/10Mio.dat";
const string TARGET = @"d:\stuff\10Mio.dat";
// Time needed to restore network connection
TimeSpam TIMEOUT = TimeSpan.FromSeconds(30);
DownloadFile(SRC,TARGET, TIMEOUT); // might want to await this to handle exceptions

更新以回应评论。如果您希望根据收到的数据进行超时,而不是基于整个操作时间,则Task.Delay也可以。例如:

static async Task DownloadFile(string url, string output, TimeSpan timeout)
{
    using (var wcl = new WebClient())
    {
        wcl.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
        DateTime? lastReceived = null;
        wcl.DownloadProgressChanged += (o, e) =>
        {
            lastReceived = DateTime.Now;
        };
        var download = wcl.DownloadFileTaskAsync(url, output);
        // await two tasks - download and delay, whichever completes first
        // do that until download fails, completes, or timeout expires
        while (lastReceived == null || DateTime.Now - lastReceived < timeout) {
            await Task.WhenAny(Task.Delay(1000), download); // you can replace 1 second with more reasonable value
            if (download.IsCompleted || download.IsCanceled || download.Exception != null)
                break;
        }
        var exception = download.Exception; // need to observe exception, if any
        bool cancelled = !download.IsCompleted && exception == null;

        // download is not completed yet, nor it is failed - cancel
        if (cancelled)
        {
            wcl.CancelAsync();
        }

        if (cancelled || exception != null)
        {
            // delete partially downloaded file if any (note - need to do with retry, might not work with a first try, because CancelAsync is not immediate)
            int fails = 0;
            while (true)
            {
                try
                {
                    File.Delete(output);
                    break;
                }
                catch
                {
                    fails++;
                    if (fails >= 10)
                        break;

                    await Task.Delay(1000);
                }
            }
        }
        if (exception != null)
        {
            throw new Exception("Failed to download file", exception);
        }
        if (cancelled)
        {
            throw new Exception($"Failed to download file (timeout reached: {timeout})");
        }
    }
}

答案 1 :(得分:1)

就个人而言,如果我要制作一个强大的下载解决方案,我会添加一个网络连接监视器,因为这是我们实际等待的内容。为简单起见,这样的事情就足够了。

online = true;

NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged;
_isNetworkOnline = NetworkInterface.GetIsNetworkAvailable();

void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
    online  = e.IsAvailable;
}

然后,您可以实际检查网络可用性并在尝试下载或升级之前等待...我绝对会接受一个简单的ping解决方案似乎比根据经验更好地工作。

根据您下载的内容的大小,监控网络速度也可能有所帮助,因此您可以决定在连接断断续续时如何分块。看看this project的想法。