簡體   English   中英

將進度報告到進度條時異步復制大文件(例如?)

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

我需要復制一些大文件,並通過進度條將進度報告回UI。

我購買了“ C#5.0”。 我在第597頁。我一直在閱讀有關並行編程的文章。 根據本書中的一些示例,我試圖完成我認為很簡單的事情,但是我真的很努力。 我的理解上一定有差距。 這就是為什么我要發布此問題。

我調查了后台工作人員,但是在嘗試獲得進展時發現自己遇到了跨線程編譯器錯誤。

我研究了異步命令,但發現自己對lambda表達式有誤解。 那或如何通過單擊按鈕異步地實際執行任務的代碼,同時仍將進度報告回UI線程。

我在MSDN上的代碼項目中傾注了許多現有的問題/答案,在這里我自己問了幾個問題,並否決了它們。 我只需要一個簡單的例子,我就可以全神貫注,我會順利進行的。

我確信我的答案是在異步,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!";
                    }
                });
            });
        }
    }
}

這應該可以幫助您入門:

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. 使用異步/等待進行異步編程
  2. 使用匿名方法(lambda)回調報告方法的進度

基本上所有這些代碼所做的是:

  1. 使用字典表示要復制的文件位置(當前和新的)
  2. 遍歷每個文件,並使用文件流和異步復制功能執行復制
  3. 通過回調報告總體進度

示例類“ MyUI”只是winforms或WPF窗口的簡化版本,帶有一個將其全部關閉的按鈕(在click事件處理程序中)和進度條。


一些注意事項

無需啟動新線程,您可以保留TPL(任務並行化庫)來為您處理調度,盡管通常示例中的所有代碼通常都在UI線程上運行。 不是問題,因為異步/等待“魔術”可確保在復制操作期間不會阻止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. 如果您想以更好的方式做到這一點,那么通過緩沖區讀取大塊數據將是一個好方法,那里有很多關於如何執行此操作的教程。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM