简体   繁体   English

将进度报告到进度条时异步复制大文件(例如?)

[英]Asynchronously copy large files while reporting progress to a progress bar (example?)

I need to copy a number of large files and report progress back to the UI via a progress bar. 我需要复制一些大文件,并通过进度条将进度报告回UI。

I bought "C# 5.0 in a Nutshell". 我购买了“ C#5.0”。 I'm on page 597. I have been reading about parallel programming. 我在第597页。我一直在阅读有关并行编程的文章。 I am trying to accomplish what in my mind is quite simple based on some examples in this book, but I'm really struggling. 根据本书中的一些示例,我试图完成我认为很简单的事情,但是我真的很努力。 There must be some gaps in my understanding. 我的理解上一定有差距。 This is why I'm posting this question. 这就是为什么我要发布此问题。

I have looked into background workers, but find myself running into cross-threading compiler errors when I try to get progress. 我调查了后台工作人员,但是在尝试获得进展时发现自己遇到了跨线程编译器错误。

I've looked into async commands but find myself misunderstanding lambda expressions. 我研究了异步命令,但发现自己对lambda表达式有误解。 That, or how to actually execute the task's code asynchronously from a button click, while still reporting progress back to the UI thread. 那或如何通过单击按钮异步地实际执行任务的代码,同时仍将进度报告回UI线程。

I have poured over many of the existing questions/answers here, on MSDN, codeproject, asked a few questions myself here and had them downvoted. 我在MSDN上的代码项目中倾注了许多现有的问题/答案,在这里我自己问了几个问题,并否决了它们。 I just need one simple example I can wrap my brain around and I'll be well on my way. 我只需要一个简单的例子,我就可以全神贯注,我会顺利进行的。

I'm confident that my answer is in async, Task.Run, File.Copy (maybe StreamReader/StreamWriter classes) and IProgress. 我确信我的答案是在异步,Task.Run,​​File.Copy(可能是StreamReader / StreamWriter类)和IProgress中。 The questions / answers I've found in my two weeks of research and trial and error are either incomplete, or too broad / too specific for some given scenario. 在我为期两周的研究和反复试验中发现的问题/答案要么不完整,要么对于某些给定场景而言过于宽泛/过于具体。

I just need one working example of a UI with a progress bar, and a button that executes code in a new thread to copy a set of large files (or just one large file) and reports back progress. 我只需要一个带有进度条的UI的工作示例,以及一个在新线程中执行代码的按钮即可复制一组大文件(或仅一个大文件)并报告进度。 From there, I can play with it and tweak it to my needs and further my overall understanding. 从那里,我可以使用它并根据自己的需要进行调整,以进一步提高我的整体理解。

Code adapted from Clint's answer, but still not updating progress correctly 根据克林特的答案改编的代码,但仍无法正确更新进度

This adaptation copies the file in an asynchronous task but updates progress from 0 to 100% only after the file has copied. 这种调整会在异步任务中复制文件,但仅在复制文件后才将进度从0更新到100%。 Because I'm working with large files, processing progress based on number of files is not sufficient. 因为我正在处理大文件,所以基于文件数量的处理进度还不够。

So far, nothing I have found or tried addresses performing a copy asynchronously whilst updating the byte for byte progress %-age of a large file. 到目前为止,我没有发现或尝试过地址在大文件的字节进度%-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!";
                    }
                });
            });
        }
    }
}

This ought to get you started: 这应该可以帮助您入门:

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

This was written by hand without visual studio, so there may be some issues (spelling etc). 这是在没有Visual Studio的情况下手动编写的,因此可能存在一些问题(拼写等)。

The core concepts are: 核心概念是:

  1. Using async/await for asynchronous programming 使用异步/等待进行异步编程
  2. Using an anonymous method (lambda) callback for reporting progress out of the method 使用匿名方法(lambda)回调报告方法的进度

Essentially all this code does is: 基本上所有这些代码所做的是:

  1. Uses a dictionary to represent file locations to copy (current and new) 使用字典表示要复制的文件位置(当前和新的)
  2. Goes through each of them and performs the copy using file streams and the asynchronous copy function 遍历每个文件,并使用文件流和异步复制功能执行复制
  3. Reports the overall progress via a callback 通过回调报告总体进度

The example class "MyUI" is just a very stripped down version of either a winforms or WPF window, with a button that starts it all off (in the click event handler) and a progress bar. 示例类“ MyUI”只是winforms或WPF窗口的简化版本,带有一个将其全部关闭的按钮(在click事件处理程序中)和进度条。


A few notes 一些注意事项

There is no need to start a new thread, you can leave the TPL (Task Parellelisation Library) to handle scheduling for you, though typically all of the code in the example here runs on the UI thread anyway. 无需启动新线程,您可以保留TPL(任务并行化库)来为您处理调度,尽管通常示例中的所有代码通常都在UI线程上运行。 This is not a problem, as the async/await "magic" ensures the UI thread isn't blocked during the copy operations. 不是问题,因为异步/等待“魔术”可确保在复制操作期间不会阻止UI线程。

IProgress is great for a more generic mechanism where you need to report deep down from within a call hierarchy, if you're only going one level up (as in my example), a simple callback will suffice. IProgress非常适合一种更通用的机制,在这种机制中,您需要从调用层次结构中深入报告,如果您仅上一层(如我的示例),则简单的回调就足够了。

A dictionary was used to simply illustrate the problem, in reality you'd probably want to use an IEnumerable<Tuple<string,string>> or an IEnumerable<MyTypeHere> to represent the desired operations. 使用字典只是为了说明问题,实际上,您可能希望使用IEnumerable<Tuple<string,string>>IEnumerable<MyTypeHere>表示所需的操作。


Super basic byte-by-byte copying 超级基本的逐字节复制

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

Here is some super simple pseudocode to illustrate the basics of copying byte-by-byte and reporting the progress for the file. 这是一些超级简单的伪代码,用于说明逐字节复制和报告文件进度的基础。

  1. Don't forget to dispose the streams (flushing the out stream before closing is a good idea) 别忘了处理流(关闭前冲洗输出流是一个好主意)
  2. If you want to do this in a better fashion, reading the data in chunks via a buffer would be a good way to go, there are plenty of tutorials out there on how to do that. 如果您想以更好的方式做到这一点,那么通过缓冲区读取大块数据将是一个好方法,那里有很多关于如何执行此操作的教程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM