2017-02-23 42 views
12

Ağ bağlantısı 3 veya daha fazla dakika boyunca kaybolduğunda aşağıdaki WPF kodu sonsuza kadar askıda kalıyor. Bağlantı yeniden kurulduğunda, ne yükler ne de indirmeye devam eder veya zaman aşımına uğrar. Ağ bağlantısı daha kısa bir süre için kaybolursa, yarım dakika söyleyin, bağlantı geri yüklendikten sonra atar. Ağ kesintisi yaşamak için daha sağlam nasıl yapabilirim?.Net DownloadFileTaskAsync sağlam WPF kodu

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

GÜNCELLEME

Güncel çözüm DownloadProgressChangedEventHandler yılında Timer yeniden başlatarak bağlı, yani zamanlayıcı yangınlar hiçbir DownloadProgressChanged olaylar zaman aşımı süresi içinde meydana yalnızca edilir. Çirkin bir hack gibi görünüyor, hala daha iyi bir çözüm arıyor.

using System; 
using System.Net; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows; 

namespace WebClientAsync 
{ 

    public partial class MainWindow : Window 
    { 

     const string SRC = "http://ovh.net/files/10Mio.dat"; 
     const string TARGET = @"d:\stuff\10Mio.dat"; 
     // Time needed to restore network connection 
     const int TIMEOUT = 30 * 1000; 

     public MainWindow() 
     { 
      InitializeComponent(); 
     } 

     private async void btnDownload_Click(object sender, RoutedEventArgs e) 
     { 
      btnDownload.IsEnabled = false; 
      btnDownload.Content = "Downloading " + SRC; 
      CancellationTokenSource cts = new CancellationTokenSource(); 
      CancellationToken token = cts.Token; 
      Timer timer = new Timer((o) => 
       { 
        // Force async cancellation 
        cts.Cancel(); 
       } 
       , null //state 
       , TIMEOUT 
       , Timeout.Infinite // once 
      ); 
      DownloadProgressChangedEventHandler handler = (sa, ea) => 
       { 
        // Restart timer 
        if (ea.BytesReceived < ea.TotalBytesToReceive && timer != null) 
        { 
         timer.Change(TIMEOUT, Timeout.Infinite); 
        } 

       }; 
      btnDownload.Content = await DownloadFileTA(token, handler); 
      // Note ProgressCallback will fire once again after awaited. 
      timer.Dispose(); 
      btnDownload.IsEnabled = true; 
     } 

     private async Task<string> DownloadFileTA(CancellationToken token, DownloadProgressChangedEventHandler handler) 
     { 
      string res = null; 
      WebClient wcl = new WebClient(); 
      wcl.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials; 
      wcl.DownloadProgressChanged += handler; 
      try 
      { 
       using (token.Register(() => wcl.CancelAsync())) 
       { 
        await wcl.DownloadFileTaskAsync(new Uri(SRC), TARGET); 
       } 
       res = "Downloaded"; 
      } 
      catch (Exception ex) 
      { 
       res = ex.Message + Environment.NewLine 
        + ((ex.InnerException != null) ? ex.InnerException.Message : String.Empty); 
      } 
      wcl.Dispose(); 
      return res; 
     } 
    } 
} 
+2

İndirme kodunu içerecek yeni bir Sınıf oluşturmanızı öneriyorum, daha sonra btnDownload_Click sınıfından DownloadFileTA yöntemini çağırın. Ardından kodu temizleyebilir ve daha kolay hata ayıklayabilirsiniz. – Tony

cevap

9

Bu yükleme için uygun zaman aşımını uygulamanız gerekir. Ama sadece Örneğin Task.Delay ve Task.WaitAny. kullanın zamanlayıcı kullanmak gerekmez:

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

Kullanımı: yorumlarınız cevaben

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 

Güncelleme. Tüm işlem süresinde değil, alınan verilere göre zaman aşımı yapmak isterseniz, Task.Delay ile de mümkündür. Örneğin: Ben sağlam indirme çözümü yapmak olsaydı o aslında neyi bekliyorsunuz çünkü

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

Gerçekten indirme işleminin ne kadar süreceği umrumda değil. Bir bağlantı kaybolduğunda/geri yüklendiğinde ve indirme işlemi birkaç kez devam ederse, sorun yoktur. Belirli bir zaman aşımı için indirme işlemi devam etmediğinde, 'WebClient'in benim için yapamayacağı durumu tespit etmem gerekiyor. Ama fikir için teşekkürler, ben bir döngüye yerleştirerek, Task.WhenAny (Task.Delay (timeout), download) beklemek adapte çalışacağız, { IsInProgress = false; bekliyor Task.WhenAny (Task.Delay (TIMEOUT), indirme); } (IsInProgress &&! Download.IsCompleted); – Serg

+0

@Serg Ben aynı yapıları kullanarak başa çıkmak için bir yolu ile yanıt güncelleştirdim. – Evk

+0

Bunu deneyeceğim, thx. – Serg

1

Şahsen, ben Ağ bağlantısı monitörü eklersiniz. Basitlik için, böyle bir şey yeterli olacaktır.

online = true; 

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

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

Sonra aslında ağ kullanılabilirliği için kontrol ve indirmek veya ilerleme denemeden önce uygun bekle ... Kesinlikle basit bir ping çözüm deneyime dayalı zamanlarda bu daha iyi iş gibi görünüyor olduğunu kabul edecektir edebilir.

İndirdiğiniz öğenin boyutuna bağlı olarak, ağ hızını izlemek de yardımcı olabilir; böylece, kesikli bağlantılar durumunda nasıl yığınlanacağına karar verebilirsiniz. Fikirler için this project'a bir göz atın.

+0

" NetworkAvailabilityChanged ", bir kullanıcı tutuklu bir portalın arkasındayken/geçtiğinde yanıyor mu? – Serg

+1

Aha, no ... Bu yüzden bir ping çözümünün gerçek hayatta daha iyi çalıştığını söyledim, çünkü esir portallar, proxy'ler vb. Üç başarısızlıktan sonra kullanılamayacak olan basit ping sınıfı, bazı iyi sonuçlar verecektir. 'NetworkAvailabilityChanged', ping testinin yapılmasının gerekip gerekmediğini belirlemeye yardımcı olur. –