我正在尝试异步/等待和进度报告,因此写了一种异步文件副本方法,该方法在每个复制的MB之后报告进度:
public async Task CopyFileAsync(string sourceFile, string destFile, CancellationToken ct, IProgress<int> progress) {
var bufferSize = 1024*1024 ;
byte[] bytes = new byte[bufferSize];
using(var source = new FileStream(sourceFile, FileMode.Open, FileAccess.Read)){
using(var dest = new FileStream(destFile, FileMode.Create, FileAccess.Write)){
var totalBytes = source.Length;
var copiedBytes = 0;
var bytesRead = -1;
while ((bytesRead = await source.ReadAsync(bytes, 0, bufferSize, ct)) > 0)
{
await dest.WriteAsync(bytes, 0, bytesRead, ct);
copiedBytes += bytesRead;
progress?.Report((int)(copiedBytes * 100 / totalBytes));
}
}
}
}
在控制台应用程序中,一个随机内容的创建i文件,然后使用上述方法复制它:
private void MainProgram(string[] args)
{
Console.WriteLine("Create File...");
var dir = Path.GetDirectoryName(typeof(MainClass).Assembly.Location);
var file = Path.Combine(dir, "file.txt");
var dest = Path.Combine(dir, "fileCopy.txt");
var rnd = new Random();
const string chars = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
var str = new string(Enumerable
.Range(0, 1024*1024*10)
.Select(i => letters[rnd.Next(chars.Length -1)])
.ToArray());
File.WriteAllText(file, str);
var source = new CancellationTokenSource();
var token = source.Token;
var progress = new Progress<int>();
progress.ProgressChanged += (sender, percent) => Console.WriteLine($"Progress: {percent}%");
var task = CopyFileAsync(file, dest, token, progress);
Console.WriteLine("Start Copy...");
Console.ReadLine();
}
执行应用程序后,两个文件都是相同的,因此复制过程以正确的顺序进行。但是,控制台输出类似于:
Create File...
Start Copy...
Progress: 10%
Progress: 30%
Progress: 20%
Progress: 60%
Progress: 50%
Progress: 70%
Progress: 80%
Progress: 40%
Progress: 90%
Progress: 100%
每次我调用应用程序时,订单都会有所不同。我不了解这种行为。如果我对事件处理程序进行断点并检查每个值,则它们的顺序正确。谁能向我解释一下?
我想稍后在带有进度栏的GUI应用程序中使用它,并且不想一直跳回去。
Progress<T>
创建时捕获当前SynchronizationContext
。如果没有SynchronizationContext
(例如在控制台应用程序中) - 将计划回调到线程池线程。这意味着多个回调甚至可以并行运行,当然也不能保证订单。
在UI应用程序中,发布到同步上下文大致等同于:
-
在WPF中:
Dispatcher.BeginInvoke()
-
Winforms:
Control.BeginInvoke
我不使用Winforms,但是在WPF中,多个具有相同优先级的BeginInvoke
(在这种情况下,它们具有相同的优先级),可以通过调用它们来执行:
多次开始呼叫是在同一dispatcherpriority上进行的 他们将按照呼叫的顺序执行。
我看不出为什么在Winforms Control.BeginInvoke
中可能会执行我们的订单,但我不知道像上面为WPF提供的证明。因此,我认为在WPF和Winforms中,您都可以安全地依靠按顺序执行的进度回调(前提是您在UI线程上创建了Progress<T>
实例本身,以便可以捕获上下文)。
网站注意:不要忘记将ConfigureAwait(false)
添加到您的ReadAsync
和WriteAsync
调用,以防止每次await
s。