在向进度条报告进度的同时异步复制大文件(例如?)

时间:2015-06-30 02:35:09

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

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

我在果壳中买了“C#5.0”。我在第597页。我一直在阅读并行编程。基于本书中的一些例子,我试图在脑海中完成很简单的事情,但我真的很挣扎。我的理解肯定存在一些差距。这就是我发布这个问题的原因。

我已经调查了后台工作者,但在尝试取得进展时发现自己遇到了跨线程编译器错误。

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

我已经在MSDN上编写了许多现有的问题/答案,代码项目,我在这里问了几个问题,然后将它们用于投票。我只需要一个简单的例子,我可以把我的大脑包裹起来,我会很顺利。

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

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

代码改编自Clint的答案,但仍未正确更新进度

此修改在异步任务中复制文件,但仅在文件复制后才将进度从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!";
                    }
                });
            });
        }
    }
}

1 个答案:

答案 0 :(得分: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);
    }
}

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

核心概念是:

  1. 使用async/await进行异步编程
  2. 使用匿名方法(lambda)回调来报告方法的进度
  3. 基本上所有这些代码都是:

    1. 使用字典表示要复制的文件位置(当前和新的)
    2. 浏览每个文件并使用文件流和异步复制功能
    3. 执行复制
    4. 通过回调报告整体进度
    5. 示例类&#34; MyUI&#34;它只是一个winforms或WPF窗口的精简版,带有一个按钮,可以全部启动它(在点击事件处理程序中)和进度条。

      一些注释

      不需要启动新线程,您可以离开TPL(任务Parellelisation库)来为您处理调度,尽管通常这里的示例中的所有代码都在UI线程上运行。这个不是一个问题,因为async / await&#34; magic&#34;确保在复制操作期间不阻止UI线程。

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

      字典用于简单地说明问题,实际上您可能希望使用IEnumerable<Tuple<string,string>>IEnumerable<MyTypeHere>来表示所需的操作。

      超级基本逐字节复制

      // 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. 如果你想以更好的方式做到这一点,通过缓冲区读取数据是一个很好的方法,有很多关于如何做的教程。