C# Stream.CopyToAsync with progress reporting-即使在复制完成后也会报告进度

C# Stream.CopyToAsync with progress reporting-即使在复制完成后也会报告进度,c#,asynchronous,dotnet-httpclient,.net-4.6,C#,Asynchronous,Dotnet Httpclient,.net 4.6,我构建了一个简单的控制台应用程序,可以从internet下载文件。 因为我决定使用HttpClient编写我的应用程序 基本上,我是在请求读取标题,然后使用ReadAsStreamAsync获取流,并使用CopyToAsync将其复制到本地文件 我找到了支持IProgress的流扩展方法: public static class StreamExtensions { public static async Task CopyToAsync(this Stream source, Stre

我构建了一个简单的控制台应用程序,可以从internet下载文件。
因为我决定使用HttpClient编写我的应用程序

基本上,我是在请求读取标题,然后使用
ReadAsStreamAsync
获取流,并使用
CopyToAsync
将其复制到本地文件

我找到了支持IProgress的流扩展方法:

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);
        }
    }
}
正如您所看到的,我得到了文件2已下载的信息,但我仍然从
CopyToAsync
获取进度报告,与文件1相同

因此,我有时会得到这种奇怪的控制台输出:

理想情况下,我希望比打电话时更确定:

await streamToReadFrom.CopyToAsync(streamToWriteTo, progress, source.Token,0x2000);
Debug.WriteLine(filename+" downloaded");
在获得调试信息后,不会报告任何进度(文件已下载)。我原以为等待可以解决我的问题,但事实并非如此

我怎样才能解决这个问题?作为一个临时解决方案,我在报告进度之前将Thread.Sleep添加到
CopyToAsync

以下是我目前的代码:

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);
            }
        }
    }
}
使用系统;
使用System.Collections.Generic;
使用系统诊断;
使用System.IO;
使用System.Linq;
使用System.Net.Http;
使用系统线程;
使用System.Threading.Tasks;
命名空间异步下载测试
{
班级计划
{
私有常量字符串LocalPath=@“D:\TEMP”;
静态void Main()
{
尝试
{
var filesToDownlad=新列表
{
新元组(“file1.tmp”http://ipv4.download.thinkbroadband.com/10MB.zip"),
新元组(“file2.tmp”http://ipv4.download.thinkbroadband.com/10MB.zip")
};
_合并位置=-1;
Console.CursorVisible=false;
ForEach(filesToDownlad,新的ParallelOptions{maxdegreeofpparallelism=4},doc=>
{
下载文件(doc.Item2,doc.Item1).Wait();
});
Debug.WriteLine(“下载的所有文件”);
Console.CursorVisible=true;
}
捕获(例外e)
{
控制台写入线(e);
Console.ReadLine();
}
}
私有静态只读对象控制台洛克=新对象();
专用静态int_控制台位置;
静态只读CancellationTokenSource=新CancellationTokenSource();
私有静态异步任务下载文件(字符串url、字符串文件名)
{
int currenctLineNumber=0;
int currenctprogress=0;
尝试
{
锁(控制台锁)
{
_consolePosition++;
currenctLineNumber=_consolePosition;
}
长文件大小=-1;
i进度=新进度(值=>
{
十进制tmp=(十进制)(值*100)/文件大小;
如果(tmp!=当前进度&&tmp>当前进度)
{
锁(控制台锁)
{
当前进度=(int)tmp;
Console.CursorTop=currenctLineNumber;
Console.CursorLeft=0;
Write(“{0,10}-{2,11}-{1,6:N2}%”,文件名,tmp,“下载”);
}
WriteLine(“{1}{0:N2}%”,tmp,文件名);
}
});
使用(HttpClient=new HttpClient())
{
使用(httpresponsemessageresponse=wait 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(“\”,”);
WriteLine(“实名:{0}”,tmp);
}
使用(Stream streamToReadFrom=wait response.Content.ReadAsStreamAsync())
{
使用(Stream streamToWriteTo=File.Open(Path.Combine(LocalPath,filename)、FileMode.Create、FileAccess.Write))
{
等待streamToReadFrom.CopyToAsync(streamToWriteTo,progress,source.Token,0x2000);
Debug.WriteLine(文件名+“已下载”);
锁(控制台锁)
{
Console.CursorTop=currenctLineNumber;
Console.CursorLeft=0;
var oldColor=Console.ForegroundColor;
Console.ForegroundColor=ConsoleColor.Green;
Write(“{0,10}-{2,11}-{1,6:N2}%”,文件名,100,“SUCCESS”);
Console.ForegroundColor=oldColor;
}
}
}
}
}
}
捕获(例外e)
{
Debug.WriteLine(e.Message);
锁(控制台锁)
{
Console.CursorTop=currenctLineNumber;
Console.CursorLeft=0;
var oldColor=Console.ForegroundColor;
Console.ForegroundColor=ConsoleColor.Red;
Write(“{0,10}-{2,11}-{1,6:N2}%”,文件名,cur
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);
            }
        }
    }
}
new Progress<long>
//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);
    }
}
IProgress<long> progress = new Progress<long>(value =>
IProgress<long> progress = new SynchronousProgress<long>(value =>
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();
    }
}
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();
    }
}