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

半腔热情 提交于 2020-04-22 05:15:29

问题


I need to copy a number of large files and report progress back to the UI via a progress bar.

I bought "C# 5.0 in a Nutshell". I'm on page 597. I have been reading about parallel programming. 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. That, or how to actually execute the task's code asynchronously from a button click, while still reporting progress back to the UI thread.

I have poured over many of the existing questions/answers here, on MSDN, codeproject, asked a few questions myself here and had them downvoted. 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. 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. 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. 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.

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:


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).

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

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.


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. This is not a problem, as the async/await "magic" ensures the UI thread isn't blocked during the copy operations.

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.

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.


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.


来源:https://stackoverflow.com/questions/31128617/asynchronously-copy-large-files-while-reporting-progress-to-a-progress-bar-exam

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!