带进度条的异步目录复制

时间:2016-12-14 09:15:48

标签: async-await file-copying

我目前正在创建一个应用程序,允许我选择一个目录,并根据您选择的内容将其复制到最多4个不同的位置

Image of app

当它复制主要用于锁定时,我希望得到在不同线程上运行的目录副本以阻止这种情况发生。最重要的是,我需要链接每个目录副本的进度条以显示传输的进度。

当涉及到c#时,我有点生疏,如果有人能指出我正确的方向,我会很喜欢它。我尝试使用await / async但它仍然只是在彼此之后不同时运行每个目录副本。

     private void button1_Click(object sender, EventArgs e)
    {
        //
        // This event handler was created by double-clicking the window in the designer.
        // It runs on the program's startup routine.
        //
        Path.GetFileName(folderBrowserDialog1.SelectedPath);
        folderBrowserDialog1.RootFolder = Environment.SpecialFolder.MyComputer;
        folderBrowserDialog1.SelectedPath = @"C:\Delivery";
        DialogResult result = folderBrowserDialog1.ShowDialog();
        if (result == DialogResult.OK)
        {
            //
            // The user selected a folder and pressed the OK button.
            // We print the number of files found.
            //
            //  string[] files = Directory.GetFiles(folderBrowserDialog1.SelectedPath);
            //  MessageBox.Show("Files found: " + files.Length.ToString(), "Message");

            if (checkBox1.Checked == true)
            {

                DirectoryCopy(folderBrowserDialog1.SelectedPath, "C:\\temp\\1\\" + Path.GetFileName(folderBrowserDialog1.SelectedPath), true);

            }
            if (checkBox2.Checked == true)
            {

                 DirectoryCopy(folderBrowserDialog1.SelectedPath, "C:\\temp\\2\\" + Path.GetFileName(folderBrowserDialog1.SelectedPath), true);
            }
            if (checkBox3.Checked == true)
            {

                 DirectoryCopy(folderBrowserDialog1.SelectedPath, "C:\\temp\\3\\" + Path.GetFileName(folderBrowserDialog1.SelectedPath), true);
            }
            if (checkBox4.Checked == true)
            {

                 DirectoryCopy(folderBrowserDialog1.SelectedPath, "C:\\temp\\4\\" + Path.GetFileName(folderBrowserDialog1.SelectedPath), true);
            }
            if (checkBox5.Checked == true)
            {

                 DirectoryCopy(folderBrowserDialog1.SelectedPath, "C:\\temp\\5\\"+ Path.GetFileName(folderBrowserDialog1.SelectedPath), true);
            }
            MessageBox.Show("These folders have been successfully transfered");
        }
    }

1 个答案:

答案 0 :(得分:0)

您的问题有几个问题:

  • 如何使用async- await复制目录
  • 如何在对话框中显示进度
  • 所选方法是否有效?

如何使用async-await复制目录

每个使用async-await的函数都必须返回Task而不是voidTask<TResult>而不是TResult。有一个例外:异步事件处理程序。事件处理程序返回void。

MSDN about Asynchronous File I/O附带以下内容:

public class FileCopier
{
    public System.IO.FileInfo SourceFile {get; set;}
    public System.IO.DirectoryInfo DestinationFolder {get; set;}

    public async Task CopyAsync()
    {
        // TODO: throw exceptions if SourceFile / DestinationFile null
        // TODO: throw exception if SourceFile does not exist
        string destinationFileName = Path.Combine(DestinationFoler.Name, SourceFile.Name);
        // TODO: decide what to do if destinationFile already exists

         // open source file for reading
         using (Stream sourceStream = File.Open(SourceFile, FileMode.Open))
         {
             // create destination file write
             using (Stream destinationStream = File.Open(DestinationFile, FileMode.CreateNew))
             {
                 await CopyAsync(sourceStream, destinationStream);
             }
        }

        public async Task CopyAsync(Stream Source, Stream Destination) 
        { 
            char[] buffer = new char[0x1000]; 
            int numRead; 
            while ((numRead = await Source.ReadAsync(buffer, 0, buffer.Length)) != 0) 
            {
                await Destination.WriteAsync(buffer, 0, numRead);
            } 
        } 
    }
}

你的目录复印机可能是这样的:

class FolderCopier
{
    public System.IO.DirectoryInfo SourceFolder {get; set;}
    public System.IO.DirectoryInfo DestinationFolder {get; set;}

    public Task CopyAsync()
    {
        foreach (FileInfo sourceFile in SourceFolder.EnumerateFiles())
        {
            var fileCopier = new FileCopier()
            {
                Sourcefile = sourceFile,
                DestinationFolder = this.DestinationFolder,
            };
            await fileCopier.CopyAsync();
        }
    }
}

最后你的事件处理程序:

private async void OnButtonDeploy_Clicked(object sender, ...)
{
    DirectoryInfo sourceFolder = GetSourceFolder(...);
    IEnumerable<DirectoryInfo> destinationFolders = GetDestinationFolders();
    IEnumerable<DirectoryCopier> folderCopiers = destinationFolders
        .Select(destinationFolder => new FolderCopier()
        {
            SourceFolder = sourceFolder,
            DestinationFolder = destinationFolder,
        });

    // if you want to copy the folders one after another while still keeping your UI responsive:
    foreach (var folderCopier in folderCopiers)
    {
        await folderCopier.CopyAsync();
    }

    // or if you want to start copying all:
    List<Task> folderCopyTasks = new List<Task>();
    foreach (var folderCopier in folderCopiers)
    {
        folderCopyTasks.Add(folderCopier.CopyAsync());
    }
    await Task.WhenAll(folderCopyTasks);
}

您的用户界面中的反馈

如果足以更新每个复制文件的进度条,请考虑在复制文件时让FolderCopier引发事件。

public class FileCopiedEventArgs
{
    public string DestinationFolder {get; set;}
    public int NrOfFilesCopied {get; set;}
    public int NrOfFilesToCopy {get; set;}
}

class FolderCopier
{
    public System.IO.DirectoryInfo SourceFolder {get; set;}
    public System.IO.DirectoryInfo DestinationFolder {get; set;}

    public Task CopyAsync()
    {
        List<FileInfo> filesToCopy = DestinationFolder.EnumerateFiles().ToList();

        for (int i=0; i<filesToCopy.Count; ++i)
        {
            // copy one file as mentioned before
            // notify listeners:
            this.OnFileCopied(i, filesToCopy.Count);
        }

        public event EventHandler<FileCopyiedEventArgs> EventFileCopied;
        public void OnFileCopied(int nrOfCopiedFiles, int filesToCopy)
        {
            var tmpEvent = this.EventFileCopied;
            if (tmpEvent != null)
            {
                tmpEvent.Invoke(this, new FileCopiedEventArgs()
                {
                    DestinationFolder = this.DestinationFolder.Name,
                    NrOfFilesCopied = nrOfCopiedFiles,
                    NrOfFilesToCopy = filesToCopy
                });
            }

            // instead of checking for null you can use the null-coalescent operator:
            this.EventFileCopied?.Invocke(this, new ...);
        }
    }
}

注册这些活动:

private async void OnButtonDeploy_Clicked(object sender, ...)
{
    var folderCopier = new FolderCopier(...);
    folderCopier.EventFileCopied += eventFileCopied;
}

private void EventFileCopied(object sender, ...)
{
    // for you to solve: if one copier has already copied 10%
    // and another copier only 2%, what value should the progress bar have?
}

文件复制期间的效率

您希望将每个源文件复制到多个位置。如果在单独的进程中执行此操作,则每个目标文件将读取一次源文件。读取源文件一次并将其写入所有目标文件似乎更有效。

class MultiFileCopier
{
    public System.IO.FileInfo SourceFile {get; set;}
    public IEnumerable<System.IO.FileInfo> DestinationFiles {get; set;}

    public async Task CopyAsync(object sender, RoutedEventArgs e)
    {
        // open your source file as a streamreader
        using (Stream sourceStream = File.Open(SourceFile, Open))
        {
            // open all destination files for writing:
            IEnumerable<Stream> destinationStreams = this.OpenDestinationsForWriting();
            await CopyFilesAsync(sourceStream, destinationStreams);
            this.DisposeDestinationStreams(destinationStreams);
         }
    }
    public async Task CopyAsync(Stream Source, IEnumerable<Stream> destinations) 
        { 
            char[] buffer = new char[0x1000]; 
            int numRead; 
            while ((numRead = await Source.ReadAsync(buffer, 0, buffer.Length)) != 0) 
            {
                // write one after another:
                foreach (var destination in destinations)
                {
                    await Destination.WriteAsync(buffer, 0, numRead);
                }
                // or write to them all at once:
                List<Task> writeTasks = new List<Task>();
                foreach (var destination in destinations)
                {
                    writeTasks.Add(Destination.WriteAsync(buffer, 0, numRead));
                }
                await Task.WhenAll(writeTasks);
            } 
        } 
    }
}

在我的所有示例中,我使用纯async-await而不启动新线程。只涉及一个线程(或者更确切地说:一次只有一个线程)。这怎么能让你的过程更快?

this interview Eric Lippert比较异步等待与必须准备晚餐的厨师团队(在文章中间的某处搜索async-await)。

在Eric Lippert的比喻中,如果厨师必须等待面包吐司,他就不会懒得做什么。相反,他环顾四周,看看他是否可以做其他事情。过了一会儿,面包烤了,他继续加工烤面包,或者是一群厨师:他的一个同事处理烤好的面包。

同样在async-await中。你的线程一次只能做一件事,只要他很忙,他就什么都做不了。在目录复制期间,您的线程有几次等待,即在读取源文件期间和写入目标文件期间。因此,如果您告诉您的线程执行其他操作而不是等待,则可能会加快此过程。

乍一看似乎async-await会帮助你:在等待写入第一个文件时,你的线程可能会开始读取你的第二个文件。唉,写入文件的设备可能与读取文件的设备相同,因此您的设备太忙,无法处理读取第二个文件的请求。因此,如果您使用async-await,我不确定您的进程是否会快得多。

同样的,如果你真的开始了几个线程。如果写入不同的设备,它只会更快。

如果您只想保持UI响应,请考虑使用BackGroundWorker类。更容易启动和停止,更容易报告进度。