带进度条的异步目录复制
Asynchronous Directory Copy with Progress Bar
我目前正在创建一个应用程序,允许我 select 一个目录并将其复制到最多 4 个不同的位置,具体取决于您 selected
Image of app
当它复制 main for 锁时,我想在不同线程上获取目录副本 运行ning 以阻止这种情况发生。最重要的是,我需要 link 提高每个目录副本的进度条以显示传输进度。
我对 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");
}
}
你的问题有几个问题:
- 如何使用 async-await 复制目录
- 如何在对话框中显示进度
- 选择的方法是否有效?
如何使用 async-await 复制目录
每个使用 async-await 的函数都必须 return Task
而不是 void
和 Task<TResult>
而不是 TResult
。有一个例外:异步事件处理程序。事件处理程序 return 无效。
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);
}
在您的UI
中反馈
如果为每个复制的文件更新进度条就足够了,请考虑让 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);
}
}
}
}
在我的所有示例中,我都使用纯异步等待而不启动新线程。只涉及一个线程(或者更准确地说:一次只涉及一个线程)。这如何使您的流程更快?
在 this interview 中,Eric Lippert 将 async await 与一组必须准备晚餐的厨师进行了比较(在文章中间的某个地方搜索 async-await)。
用 Eric Lippert 的比喻,如果厨师必须等待面包烤好,他就不会懒惰地什么都不做。相反,他环顾四周,看看是否可以做点别的事情。面包烤了一会儿后,他继续加工烤面包,或者在一组厨师中:他的一位同事加工烤面包。
async-await也是一样。你的线程一次只能做一件事,只要他忙,他就不能做其他事情。在目录复制期间,您的线程会多次等待,即在读取源文件期间和在写入目标文件期间。因此,如果您告诉您的线程做其他事情而不是等待,它可能会加快进程。
所以乍一看,async-await 似乎可以帮助您:在等待写入第一个文件时,您的线程可以开始读取您的第二个文件。 las,写入文件的设备可能与读取文件的设备相同,因此您的设备将太忙而无法处理读取第二个文件的请求。所以我不确定如果你使用 async-await,你的过程是否会更快。
如果真的开启了几个线程也是一样的。只有写入不同的设备才会更快。
如果您只想保持 UI 响应,请考虑使用 BackGroundWorker class。更容易开始和停止,更容易报告进度。
我目前正在创建一个应用程序,允许我 select 一个目录并将其复制到最多 4 个不同的位置,具体取决于您 selected
Image of app
当它复制 main for 锁时,我想在不同线程上获取目录副本 运行ning 以阻止这种情况发生。最重要的是,我需要 link 提高每个目录副本的进度条以显示传输进度。
我对 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");
}
}
你的问题有几个问题:
- 如何使用 async-await 复制目录
- 如何在对话框中显示进度
- 选择的方法是否有效?
如何使用 async-await 复制目录
每个使用 async-await 的函数都必须 return Task
而不是 void
和 Task<TResult>
而不是 TResult
。有一个例外:异步事件处理程序。事件处理程序 return 无效。
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);
}
在您的UI
中反馈如果为每个复制的文件更新进度条就足够了,请考虑让 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);
}
}
}
}
在我的所有示例中,我都使用纯异步等待而不启动新线程。只涉及一个线程(或者更准确地说:一次只涉及一个线程)。这如何使您的流程更快?
在 this interview 中,Eric Lippert 将 async await 与一组必须准备晚餐的厨师进行了比较(在文章中间的某个地方搜索 async-await)。
用 Eric Lippert 的比喻,如果厨师必须等待面包烤好,他就不会懒惰地什么都不做。相反,他环顾四周,看看是否可以做点别的事情。面包烤了一会儿后,他继续加工烤面包,或者在一组厨师中:他的一位同事加工烤面包。
async-await也是一样。你的线程一次只能做一件事,只要他忙,他就不能做其他事情。在目录复制期间,您的线程会多次等待,即在读取源文件期间和在写入目标文件期间。因此,如果您告诉您的线程做其他事情而不是等待,它可能会加快进程。
所以乍一看,async-await 似乎可以帮助您:在等待写入第一个文件时,您的线程可以开始读取您的第二个文件。 las,写入文件的设备可能与读取文件的设备相同,因此您的设备将太忙而无法处理读取第二个文件的请求。所以我不确定如果你使用 async-await,你的过程是否会更快。
如果真的开启了几个线程也是一样的。只有写入不同的设备才会更快。
如果您只想保持 UI 响应,请考虑使用 BackGroundWorker class。更容易开始和停止,更容易报告进度。