【问题标题】:Task cancellation: while loop not exited properly when ThrowIfCancellationRequested() is called任务取消:调用 ThrowIfCancellationRequested() 时未正确退出 while 循环
【发布时间】:2017-08-21 11:58:41
【问题描述】:

我开发了一个小项目(使用 MVVM),具有将文件上传到 FTP 服务器的功能。

用户可以查看上传进度:完成百分比由 vm.Busycontent 向用户显示,这是我的视图模型中的一个属性,绑定到我的视图中的 UI 元素。

这是读取文件并通过 FTP 上传的代码(它是任务 vm.FtpUploadTask 的一部分)

   using (FileStream inputStream = File.OpenRead(file))
        {
            using (outputStream = request.GetRequestStream())
            {
                var buffer = new byte[1024 * 1024];
                int totalReadBytesCount = 0;
                int readBytesCount;

                while ((readBytesCount = inputStream.Read(buffer, 0, buffer.Length)) > 0 && (!vm.Token.IsCancellationRequested))
                {
                    vm.Token.ThrowIfCancellationRequested();

                    outputStream.Write(buffer, 0, readBytesCount);
                    totalReadBytesCount += readBytesCount;
                    var progress = totalReadBytesCount * 100.0 / inputStream.Length;
                    vm.BusyContent = ((int)progress).ToString();
                }
            }
        }            

MainWindow.xaml

我正在使用 WPF 扩展工具包 BusyIndi​​cator

<xctk:BusyIndicator IsBusy='{Binding IsBusy}'>
  <xctk:BusyIndicator.BusyContentTemplate>
    <DataTemplate>
      <StackPanel>
        <TextBlock Text='{Binding Path=DataContext.BusyContent,
            RelativeSource={RelativeSource AncestorType={x:Type Window}}}' />
      </StackPanel>
    </DataTemplate>
  </xctk:BusyIndicator.BusyContentTemplate>
</xctk:BusyIndicator>    

UploadToFTPCommand.cs

try
{
    vm.FtpUploadTask = new Task(() => FTPUpload(file), vm.Token);

    vm.FtpUploadTask.Start();
    vm.FtpUploadTask.Wait(vm.Token);

    vm.BusyContent = "upload done!";

}
catch (OperationCanceledException)
{
    vm.BusyContent = "Canceled";
}

CancelCommand.cs

public class CancelCommand : ICommand
{
    public void Execute(object parameter)
    {
        vm.TokenSource.Cancel();
    }
}

取消功能有效,但有时 vm.Busycontent 等于

  • ((int)progress).ToString()
  • 在 uploadftp 命令中取消

当按下 Cancel 按钮时,while 循环应该退出并且用户应该只看到 catch 中的消息 (OperationCanceledException)。 任何想法如何解决这个问题?

注意事项

  • 我使用的是 .NET 4.0
  • 该程序是一个较大项目的一部分,其中包括多个应以同步方式执行的任务。这就是我使用 Task.Start() 和 Task.Wait() 方法的原因。

编辑 问题依旧

using (FileStream inputStream = File.OpenRead(file))
{
using (outputStream = request.GetRequestStream())
{
    var buffer = new byte[1024 * 1024];
    int totalReadBytesCount = 0;
    int readBytesCount;

    while ((readBytesCount = inputStream.Read(buffer, 0, buffer.Length)) > 0)
    {
        if (vm.Token.IsCancellationRequested)
        {
            break;
        }
        outputStream.Write(buffer, 0, readBytesCount);
        totalReadBytesCount += readBytesCount;
        var progress = totalReadBytesCount * 100.0 / inputStream.Length;
        vm.BusyContent = ((int)progress).ToString();
    }
    if (vm.Token.IsCancellationRequested)
    {

        inputStream.Close();
        outputStream.Close();
        vm.Token.ThrowIfCancellationRequested();
    }
}

}

【问题讨论】:

  • 您正在检查Token.IsCancellationRequested 并在执行ThrowIfCancellationRequested() 之前退出循环。
  • 代码很多,能发minimal reproducible example吗?
  • @svick 我编辑我的问题以减少代码量
  • @JSteward 我已经编辑了我的代码,但问题仍然存在。有什么想法吗?

标签: wpf while-loop task-parallel-library task cancellation


【解决方案1】:

您可能会遇到与您设置代码的方式有关的问题。如果不仔细阅读它的每一行,就很难说清楚。但更重要的是:

当按下取消按钮时,应该退出while循环,用户应该只看到catch中的消息(OperationCanceledException)。

这准确显示了如何退出 while 循环并抛出可以捕获的异常。注意测试断言TaskCanceledException,但你只是抓住OperationCanceledException是对的。

using System.Threading.Tasks;
using System.IO;
using System.Threading;

using NUnit.Framework;

namespace CancellationTests {

    [TestFixture]
    public class WhileCancellation {

        [Test]
        public void While_Loop_Is_Canceled_When_Cancel_Is_Requested() {
            var inputFile = new FileInfo("somebigfile.txt");
            var outputFile = new FileInfo("outputFile.txt");
            var cts = new CancellationTokenSource();

            cts.Cancel();

            Assert.ThrowsAsync<TaskCanceledException>(() => TheWhileLoop(inputFile, outputFile, cts.Token));
        }

        private async Task TheWhileLoop(FileInfo inputFile, FileInfo outputFile, CancellationToken token) {

            using (var inputStream = inputFile.OpenRead())
            using (var outputStream = outputFile.OpenWrite()) {
                var buffer = new byte[1024 * 1024];
                var totalReadBytesCount = 0;
                var readBytesCount = 0;

                while ((readBytesCount = await inputStream.ReadAsync(buffer, 0, buffer.Length, token)) > 0) {                    
                    await outputStream.WriteAsync(buffer, 0, readBytesCount, token);
                    token.ThrowIfCancellationRequested();
                    totalReadBytesCount += readBytesCount;
                    var progress = totalReadBytesCount * 100.0 / inputStream.Length;
                    vm.BusyContent = ((int)progress).ToString();
                }
            }
        }        

        private static class vm {
            public static string BusyContent { get; set; }
        }
    }    
}

希望这能让你走上正轨。

【讨论】:

    猜你喜欢
    • 2021-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-22
    相关资源
    最近更新 更多