2016-09-28 40 views
6

İnternet'ten dosya indiren basit bir konsol uygulamaları yapıyorum.
I had problems with WebClient HttpClient kullanarak uygulamamı yazmaya karar verdim.Stream.CopyToAsync, ilerleme raporlamasıyla birlikte - kopyalama tamamlandıktan sonra bile rapor ediliyor

Temel olarak, üstbilgileri okumak için istek yapıyorum, ReadAsStreamAsync kullanarak CopyToAsync kullanarak yerel dosyaya kopyalarım akışı alıyorum.

Ben IProgress destekler akışı için uzatma yöntemi buldum:

public static class StreamExtensions 
{ 
    public static async Task CopyToAsync(this Stream source, Stream destination, IProgress<long> progress, CancellationToken cancellationToken = default(CancellationToken), int bufferSize = 0x1000) 
    { 
     var buffer = new byte[bufferSize]; 
     int bytesRead; 
     long totalRead = 0; 
     while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) 
     { 
      await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken); 
      cancellationToken.ThrowIfCancellationRequested(); 
      totalRead += bytesRead; 
      //Thread.Sleep(10); 
      progress.Report(totalRead); 
     } 
    } 
} 

My uygulama çalışmalarını, ama yanlış ilerleme bilgi alabilirsiniz.

file1.tmp 60.95% 
file2.tmp 98.09% 
file1.tmp 60.98% 
file2.tmp 98.21% 
file2.tmp 98.17% 
file2.tmp 98.25% 
file1.tmp 61.02% 
file2.tmp 98.41% 
file2.tmp downloaded 
file2.tmp 98.29% 
file2.tmp 98.37% 
file1.tmp 61.06% 
file2.tmp 89.27% 
file2.tmp 89.31% 
file2.tmp 98.33% 
file2.tmp 98.45% 
file2.tmp 98.48% 
file1.tmp 61.10% 
file1.tmp 61.14% 
file2.tmp 98.52% 
file1.tmp 61.22% 
file2.tmp 98.60% 
file2.tmp 98.56% 
file1.tmp 61.30% 
file2.tmp 98.88% 
file2.tmp 90.44% 
file1.tmp 61.53% 
file2.tmp 98.72% 
file1.tmp 61.41% 
file1.tmp 61.73% 
file2.tmp 98.80% 
file1.tmp 61.26% 
file1.tmp 61.49% 
file1.tmp 61.57% 
file1.tmp 61.69% 
... 
file1.tmp 99.31% 
file1.tmp 98.84% 
file1.tmp 98.80% 
file1.tmp 99.04% 
file1.tmp 99.43% 
file1.tmp 99.12% 
file1.tmp 99.00% 
file1.tmp downloaded 
file1.tmp 100.00% 
file1.tmp 98.73% 
file1.tmp 98.88% 
file1.tmp 99.47% 
file1.tmp 99.98% 
file1.tmp 99.90% 
file1.tmp 98.96% 
file1.tmp 99.78% 
file1.tmp 99.99% 
file1.tmp 99.74% 
file1.tmp 99.59% 
file1.tmp 99.94% 
file1.tmp 98.49% 
file1.tmp 98.53% 
ALL FILES DOWNLOADED 
file1.tmp 99.55% 
file1.tmp 98.41% 
file1.tmp 99.62% 
file1.tmp 98.34% 
file1.tmp 99.66% 
file1.tmp 98.69% 
file1.tmp 98.37% 

Sen Dosya2 indirilmesini bilgi var görebileceğiniz gibi, ama yine de Dosya1 ile aynı, CopyToAsync gelen ilerleme raporu alıyorum: Örneğin
2 dosya indirme Ben çıktı penceresinde görüyoruz. bu nedenle

Bazen bu tuhaf konsol çıktısını almak: O ayıklama bilgisini aldıktan sonra

await streamToReadFrom.CopyToAsync(streamToWriteTo, progress, source.Token,0x2000); 
Debug.WriteLine(filename+" downloaded"); 

:

enter image description here

İdeal dediğim zaman daha emin olmak istiyorum ilerleme kaydedilmedi (dosya indirildi). await'un benim sorunumu çözeceğini düşündüm, ama değil.

Bunu nasıl düzeltebilirim? Geçici bir çözüm olarak, ilerlemeyi bildirmeden hemen önce Thread.leep'ı CopyToAsync'a ekliyorum.

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.IO; 
using System.Linq; 
using System.Net.Http; 
using System.Threading; 
using System.Threading.Tasks; 

namespace AsyncDownloadTest 
{ 
    class Program 
    { 
     private const string LocalPath = @"D:\TEMP"; 

     static void Main() 
     { 
      try 
      { 
       var filesToDownlad = new List<Tuple<string, string>> 
       { 
        new Tuple<string, string>("file1.tmp", "http://ipv4.download.thinkbroadband.com/10MB.zip"), 
        new Tuple<string, string>("file2.tmp", "http://ipv4.download.thinkbroadband.com/10MB.zip") 
       }; 
       _consolePosition = -1; 
       Console.CursorVisible = false; 

       Parallel.ForEach(filesToDownlad, new ParallelOptions { MaxDegreeOfParallelism = 4 }, doc => 
       { 
        DownloadFile(doc.Item2,doc.Item1).Wait(); 
       }); 
       Debug.WriteLine("ALL FILES DOWNLOADED"); 
       Console.CursorVisible = true;  
      } 
      catch (Exception e) 
      { 
       Console.WriteLine(e); 
       Console.ReadLine(); 
      } 
     } 

     private static readonly object ConsoleLock = new object(); 
     private static int _consolePosition; 

     static readonly CancellationTokenSource source = new CancellationTokenSource(); 

     private static async Task DownloadFile(string url, string filename) 
     { 
      int currenctLineNumber = 0; 
      int currectProgress = 0; 

      try 
      { 
       lock (ConsoleLock) 
       { 
        _consolePosition++; 
        currenctLineNumber = _consolePosition; 
       } 

       long fileSize = -1; 

       IProgress<long> progress = new Progress<long>(value => 
       { 
        decimal tmp = (decimal)(value * 100)/fileSize; 

        if (tmp != currectProgress && tmp > currectProgress) 
        { 
         lock (ConsoleLock) 
         { 
          currectProgress = (int)tmp; 
          Console.CursorTop = currenctLineNumber; 
          Console.CursorLeft = 0; 
          Console.Write("{0,10} - {2,11} - {1,6:N2}%", filename, tmp, "DOWNLOADING"); 
         } 
         Debug.WriteLine("{1} {0:N2}%", tmp, filename); 
        } 
       }); 

       using (HttpClient client = new HttpClient()) 
       { 
        using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, source.Token)) 
        { 
         response.EnsureSuccessStatusCode(); 
         if (response.Content.Headers.ContentLength.HasValue) fileSize = response.Content.Headers.ContentLength.Value; 

         if (response.Content.Headers.ContentDisposition != null) 
         { 
          var tmp = response.Content.Headers.ContentDisposition.FileName.Replace("\"", ""); 
          Debug.WriteLine("Real name: {0}",tmp); 
         } 

         using (Stream streamToReadFrom = await response.Content.ReadAsStreamAsync()) 
         { 
          using (Stream streamToWriteTo = File.Open(Path.Combine(LocalPath, filename), FileMode.Create, FileAccess.Write)) 
          { 
           await streamToReadFrom.CopyToAsync(streamToWriteTo, progress, source.Token,0x2000); 

           Debug.WriteLine(filename+" downloaded"); 

           lock (ConsoleLock) 
           { 
            Console.CursorTop = currenctLineNumber; 
            Console.CursorLeft = 0; 
            var oldColor = Console.ForegroundColor; 
            Console.ForegroundColor = ConsoleColor.Green; 
            Console.Write("{0,10} - {2,11} - {1,6:N2}%", filename, 100, "SUCCESS"); 
            Console.ForegroundColor = oldColor; 
           } 
          } 
         } 
        } 
       } 
      } 
      catch (Exception e) 
      { 
       Debug.WriteLine(e.Message); 
       lock (ConsoleLock) 
       { 
        Console.CursorTop = currenctLineNumber; 
        Console.CursorLeft = 0; 
        var oldColor = Console.ForegroundColor; 
        Console.ForegroundColor = ConsoleColor.Red; 
        Console.Write("{0,10} - {2,11} - {1,6:N2}%", filename, currectProgress, "ERROR"); 
        Console.ForegroundColor = oldColor; 
       } 
      } 
     } 
    } 

    public static class StreamExtensions 
    { 
     public static async Task CopyToAsync(this Stream source, Stream destination, IProgress<long> progress, CancellationToken cancellationToken = default(CancellationToken), int bufferSize = 0x1000) 
     { 
      var buffer = new byte[bufferSize]; 
      int bytesRead; 
      long totalRead = 0; 
      while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) 
      { 
       await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken); 
       cancellationToken.ThrowIfCancellationRequested(); 
       totalRead += bytesRead; 
       Thread.Sleep(10); 
       progress.Report(totalRead); 
      } 
     } 
    } 
} 
+0

Bunun, "async" kullanılarak değil, konsolunuza çıktı verirken yanlış satırı/sütunu belirleyen kodunuzla ilgili bir şey olduğunu sanmıyorum. "Lock" unla ilgili bir şey yanlıştı, ama daha fazla araştırmadım. – Krumelur

+0

@Krumelur Yorumunuz için teşekkür ederim, konsol ile ilgili kodu kaldırdığımda bile ben hala "file2.tmp indirildi" VS Output penceresinde görebiliyorum ama bundan sonra hala sorularımda olduğu gibi ilerleme raporları alıyorum (ikinci kod bölümü) 'file1.tmp% 60.95' ile başlayan yıldızlar – Misiu

+1

Dikkatli olun 'DownloadFile (doc.Item2, doc.Item1) .Wait();' 'Parallel.ForEach' içinde,' Paralel' sınıfı arama iş parçacığı kullanır Çalışan iş parçacıklarından biri olarak, bu iş parçacığı bir "SynchronizationContext" ise, programın kilitlenmesine neden olur. 'Parallel' yerine [TPL Dataflow] 'a (https://msdn.microsoft.com/en-us/library/hh228603 (v = vs.110) .aspx) bakmak isteyebilirsiniz. Async işlevlerini desteklemeyi destekleyin, böylece '' .Filge '' yerine doğrudan '.Wait()' demeye gerek kalmadan 'DownloadFile'ı geçebilirsiniz. –

cevap

6

Senin sorunun aslında burada:

Aşağıda benim geçerli kod

new Progress<long> 

Progress<T> sınıf always invokes its callbacks in a SynchronizationContext - bu durumda parçacığı havuzu SynchronizationContext olduğunu. Bu, raporlama raporlama kodu Report'u çağırdığında, yalnızca iş parçacığı havuzuna geri çağrılması anlamına gelir. Bu yüzden, onları siparişten (veya indirme işleminin bittikten sonra birazdan) gelmekte olduğunu görmek mümkün.

Bunu düzeltmek için, IProgress<T> kendi özel uygulama oluşturabilirsiniz: OP talep

//C#6.0 
public sealed class SynchronousProgress<T> : IProgress<T> 
{ 
    private readonly Action<T> _callback; 
    public SynchronousProgress(Action<T> callback) { _callback = callback; } 
    void IProgress<T>.Report(T data) => _callback(data); 
} 
//older version 
public sealed class SynchronousProgress<T> : IProgress<T> 
{ 
    private readonly Action<T> _callback; 

    public SynchronousProgress(Action<T> callback) 
    { 
     _callback = callback; 
    } 

    void IProgress<T>.Report(T data) 
    { 
     _callback(data); 
    } 
} 

Sonra

IProgress<long> progress = new SynchronousProgress<long>(value => 
+0

Cevabınız için teşekkür ederiz. Progres kullandım çünkü işe yarayacağını düşündüm. Belki Eylem veya basit Delege daha iyi olurdu? Çözümü bir an içinde deneyeceğim ama belki de yapmaya çalıştığım şeyi yapmanın daha iyi bir yolu var. – Misiu

+0

@Misiu: Hayır, "IProgress " yanlıştır. –

1

çizgiyi

IProgress<long> progress = new Progress<long>(value => 

yerine nasıl göstermek programında TPL Dataflow ile yaptığı açıklamada. Aslında oldukça basit bir dönüşüm. Önce NuGet paketi System.Threading.Tasks.Dataflow'a bir başvuru ekleyin.Sonra sadece

bir senkronizasyon bağlamıyla bir programla bunu ederse
static void Main() 
{ 
    try 
    { 
     var filesToDownlad = new List<Tuple<string, string>> 
     { 
      new Tuple<string, string>("file1.tmp", "http://ipv4.download.thinkbroadband.com/10MB.zip"), 
      new Tuple<string, string>("file2.tmp", "http://ipv4.download.thinkbroadband.com/10MB.zip") 
     }; 
     _consolePosition = -1; 
     Console.CursorVisible = false; 

     var downloadBlock = new ActionBlock<Tuple<string, string>>(doc => DownloadFile(doc.Item2, doc.Item1), 
                    new ExecutionDataflowBlockOptions {MaxDegreeOfParallelism = 4}); 

     foreach (var file in filesToDownlad) 
     { 
      downloadBlock.Post(file); 
     } 
     downloadBlock.Complete(); 
     downloadBlock.Completion.Wait(); 


     Debug.WriteLine("ALL FILES DOWNLOADED"); 
     Console.CursorVisible = true; 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine(e); 
     Console.ReadLine(); 
    } 
} 

için ana işlevi değiştirmek ve tamamlanmasını beklemek istediğini ve bunun yerine senkronize işlemleri yapmanın gönderme yapabileceğini

static async Task Example() 
{ 
    try 
    { 
     var filesToDownlad = new List<Tuple<string, string>> 
     { 
      new Tuple<string, string>("file1.tmp", "http://ipv4.download.thinkbroadband.com/10MB.zip"), 
      new Tuple<string, string>("file2.tmp", "http://ipv4.download.thinkbroadband.com/10MB.zip") 
     }; 
     _consolePosition = -1; 
     Console.CursorVisible = false; 

     var downloadBlock = new ActionBlock<Tuple<string, string>>(doc => DownloadFile(doc.Item2, doc.Item1), 
                    new ExecutionDataflowBlockOptions {MaxDegreeOfParallelism = 4}); 

     foreach (var file in filesToDownlad) 
     { 
      await downloadBlock.SendAsync(file); 
     } 
     downloadBlock.Complete(); 
     await downloadBlock.Completion; 


     Debug.WriteLine("ALL FILES DOWNLOADED"); 
     Console.CursorVisible = true; 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine(e); 
     Console.ReadLine(); 
    } 
} 

Not, bu, "TÜM DOSYALAR DOWNLOADED" sorununuzu hemen gidermez. Stephen's solution to fix that'u kullanmanız gerekir. Tüm bunlar, bu kod çağıran iş parçacığı üzerinde bir SynchronizationContext olabilecek bir durumda çalışıyorsa, olası bir kilitlenme giderir.

+0

Bu kodu gönderdiğiniz için teşekkür ederiz. Dataflow için zor olacağını düşündüm, ancak kod açık görünüyor. DataFlow, kesinlikle bakmam gereken bir şey! – Misiu