【问题标题】:Asynchronously copy large files while reporting progress to a progress bar (example?)在向进度条报告进度的同时异步复制大文件(例如?)
【发布时间】:2015-06-30 02:35:08
【问题描述】:

我需要复制一些大文件并通过进度条将进度报告回 UI。

我买了“C# 5.0 in a Nutshell”。我在第 597 页。我一直在阅读有关并行编程的内容。根据本书中的一些示例,我正在尝试完成我认为很简单的事情,但我真的很挣扎。我的理解肯定有一些差距。这就是我发布这个问题的原因。

我研究过后台工作人员,但在尝试取得进展时发现自己遇到了跨线程编译器错误。

我研究了异步命令,但发现自己误解了 lambda 表达式。那,或者如何从按钮单击异步实际执行任务的代码,同时仍将进度报告回 UI 线程。

我在这里、MSDN、codeproject 上倾注了许多现有的问题/答案,我自己在这里问了一些问题,并让他们投了反对票。我只需要一个简单的例子,我可以把我的大脑包裹起来,我会顺利进行的。

我确信我的答案是异步、Task.Run、File.Copy(可能是 StreamReader/StreamWriter 类)和 IProgress。我在两周的研究和反复试验中发现的问题/答案要么不完整,要么对于某些特定场景来说过于宽泛/过于具体。

我只需要一个带有进度条的 UI 工作示例,以及一个在新线程中执行代码以复制一组大文件(或仅一个大文件)并报告进度的按钮。从那里,我可以使用它并根据我的需要对其进行调整,并进一步加深我的整体理解。

代码改编自克林特的回答,但仍未正确更新进度

此适配在异步任务中复制文件,但仅在文件复制后将进度从 0 更新到 100%。因为我正在处理大文件,所以基于文件数量的处理进度是不够的。

到目前为止,我还没有找到或尝试过在为大文件的字节进度 %-age 更新字节时异步执行复制的地址。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.IO;

namespace CopyProgressWorking
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            string srcFile = @"C:\temp\BigFile.txt";
            string dstFile = @"C:\temp\temp2\BigFile.txt";
            button1.Click += (s, e) => DoCopy(srcFile, dstFile);
        }

        public async Task CopyFiles(Dictionary<string, string> files, Action<int> progressCallback)
        {
            for (var x = 0; x < files.Count; x++)
            {
                var item = files.ElementAt(x);
                var from = item.Key;
                var to = item.Value;

                using (var outStream = new FileStream(to, FileMode.Create, FileAccess.Write, FileShare.Read))
                {

                    using (var inStream = new FileStream(from, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {

                        long size = inStream.Position;
                        Console.WriteLine("Filesize is {0}", size);
                        await inStream.CopyToAsync(outStream);

                    }
                }

                progressCallback((int)((x + 1) / files.Count) * 100);
            }
        }

        public async void DoCopy(string srcFile, string dstFile)
        {
            label1.Text = "Copying " + srcFile;
            await CopyFiles(new Dictionary<string, string>
            {
                {srcFile, dstFile}
            },
            prog =>
            {
                Invoke((MethodInvoker)delegate {
                    progressBar1.Value = prog;
                    if (prog >= 100)
                    {
                        label1.Text = "Copy complete!";
                    }
                });
            });
        }
    }
}

【问题讨论】:

  • 首先,您将无法使用File.Copy,除非您只想报告文件之间的进度。如果要在复制单个文件期间报告进度,则必须手动将数据从一个 FileStream 复制到另一个,或者使用非托管方法。
  • 我很乐意获取文件的总字节数或一组文件的总字节数并报告该进度。看起来 CLR 已经有了这样一种我可以利用的方法?
  • 字典 文件和 var item = files.ElementAt(x);不能真正工作,除非另有说明,否则不订购字典。
  • 你有没有最终得到这个工作?如果是这样,示例是否像您在任何地方的 github 上寻找的那样?

标签: c# multithreading asynchronous system.io.file


【解决方案1】:

这应该让你开始:

public static class Copier
{
    public static async Task CopyFiles(Dictionary<string,string> files, Action<int> progressCallback)
    {
        for(var x = 0; x < files.Count; x++)
        {
            var item = files.ElementAt(x);
            var from = item.Key;
            var to = item.Value;

            using(var outStream = new FileStream(to, FileMode.Create, FileAccess.Write, FileShare.Read))
            {
                using(var inStream = new FileStream(from, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    await inStream.CopyToAsync(outStream);
                }
            }

            progressCallback((int)((x+1)/files.Count) * 100);
        }
    }
}

public class MyUI
{
    public MyUI()
    {
        copyButton.Click += (s,e) => DoCopy();
    }

    public async void DoCopy()
    {
        await Copier.CopyFiles(new Dictionary<string,string>
        {
            {"C:\file1.txt", "C:\users\myuser\desktop\file1.txt"},
            {"C:\file2.txt", "C:\users\myuser\desktop\file2.txt"}
        }, prog => MyProgressBar.Value = prog);
    }
}

这是在没有 Visual Studio 的情况下手工编写的,因此可能存在一些问题(拼写等)。

核心概念是:

  1. 使用async/await 进行异步编程
  2. 使用匿名方法 (lambda) 回调报告方法的进度

基本上这段代码所做的就是:

  1. 使用字典表示要复制的文件位置(当前和新)
  2. 遍历它们并使用文件流和异步复制功能执行复制
  3. 通过回调报告整体进度

示例类“MyUI”只是一个非常精简的 winforms 或 WPF 窗口版本,带有一个启动它的按钮(在单击事件处理程序中)和一个进度条。


一些注意事项

无需启动新线程,您可以让 TPL(任务并行化库)为您处理调度,尽管通常此处示例中的所有代码都在 UI 线程上运行。这不是问题,因为 async/await “魔法”可确保 UI 线程在复制操作期间不会被阻塞。

IProgress 非常适合一种更通用的机制,您需要从调用层次结构中深入报告,如果您只向上一层(如我的示例中所示),一个简单的回调就足够了。

使用字典来简单说明问题,实际上您可能希望使用IEnumerable&lt;Tuple&lt;string,string&gt;&gt; 或IEnumerable&lt;MyTypeHere&gt; 来表示所需的操作。


超级基本的逐字节复制

// Assuming you've got a from and to file stream open
// here is some hand-written pseudocode (C# style) to show the basic concept
foreach(var file in files)
{
    var from = OpenFromStream(file.From);
    var to = OpenFromStream(file.To);

    var lengthOfFile = from.Length;
    for(x = 0; x < lengthOfFile; x++)
    {
        to.WriteByte(from.ReadByte());
        progress((int)(x / lengthOfFile) * 100);
    }
}

这里有一些超级简单的伪代码来说明逐字节复制和报告文件进度的基础知识。

  1. 不要忘记处理流(在关闭前冲洗流出的流是个好主意)
  2. 如果您想以更好的方式执行此操作,通过缓冲区以块读取数据将是一个不错的方法,那里有很多关于如何做到这一点的教程。

【讨论】:

  • 克林特,请看看我对你的例子的改编,并在主帖中讨论它。
  • 哦,您想要实际的复制进度吗?那么,您需要手动执行复制操作。我将使用一些指南更新我的答案。
  • 啊,这解释了为什么我一直在努力寻找一个可靠的例子。这并不像我想象的那么简单(或者应该是)。
  • @user1551027 确实,复制方法肯定没有过载,需要一个很好的进度报告参数。但是编写自己的扩展方法来完成它是相当简单的。
  • (int) 强制转换是不必要的
猜你喜欢
  • 1970-01-01
  • 2018-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-18
  • 2016-02-17
相关资源
最近更新 更多