c#async/等待进度报告不是预期的顺序



我正在尝试异步/等待和进度报告,因此写了一种异步文件副本方法,该方法在每个复制的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应用程序中,发布到同步上下文大致等同于:

  1. 在WPF中:Dispatcher.BeginInvoke()

  2. Winforms:Control.BeginInvoke

我不使用Winforms,但是在WPF中,多个具有相同优先级的BeginInvoke(在这种情况下,它们具有相同的优先级),可以通过调用它们来执行:

多次开始呼叫是在同一dispatcherpriority上进行的 他们将按照呼叫的顺序执行。

我看不出为什么在Winforms Control.BeginInvoke中可能会执行我们的订单,但我不知道像上面为WPF提供的证明。因此,我认为在WPF和Winforms中,您都可以安全地依靠按顺序执行的进度回调(前提是您在UI线程上创建了Progress<T>实例本身,以便可以捕获上下文)。

网站注意:不要忘记将ConfigureAwait(false)添加到您的ReadAsyncWriteAsync调用,以防止每次awaits。

在UI应用程序中返回UI线程

最新更新