任务取消:当调用ThrowIfCancellationRequested()时,while循环未正确退出

时间:2017-03-28 12:04:32

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

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

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

以下是用于读取文件和通过FTP上传的代码(这是Task 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)的进展)的ToString()
  • 在uploadftpcommand
  • 中取消

按下“取消”按钮时,应退出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();
    }
}

}

1 个答案:

答案 0 :(得分:0)

您可能遇到与确切设置代码的方式有关的问题。没有经过它的每一行,很难说。但更重要的是:

  

按下“取消”按钮时,应退出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; }
        }
    }    
}

希望这能让你走上正轨。