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

Wpf 任务取消:调用ThrowIfCancellationRequested()时,while循环未正确退出,wpf,while-loop,task-parallel-library,task,cancellation,Wpf,While Loop,Task Parallel Library,Task,Cancellation,我开发了一个小项目(使用MVVM),它具有将文件上载到FTP服务器的功能 用户可以查看上传进度:完成百分比通过vm.Busycontent显示给用户,vm.Busycontent是我的视图模型中绑定到我视图中的UI元素的属性 下面是读取文件并通过FTP上传的代码(这是任务vm.FtpUploadTask的一部分) main window.xaml 我正在使用WPF扩展工具包BusyIndicator <xctk:BusyIndicator IsBusy='{Binding IsBusy}'

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

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

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

main window.xaml

我正在使用WPF扩展工具包BusyIndicator

<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>    
CancelCommand.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";
}
public class CancelCommand : ICommand
{
    public void Execute(object parameter)
    {
        vm.TokenSource.Cancel();
    }
}
cancel函数起作用,但有时vm.Busycontent等于

  • ((int)progress.ToString()
  • 已在上载FTPCommand中取消
按下取消按钮时,while循环应退出,用户应仅在catch中看到消息(OperationCanceledException)。 有什么办法解决这个问题吗

注释

  • 我正在使用.NET4.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();
    }
}

}

您可能会遇到一个与您如何设置代码有关的问题。如果不仔细检查每一行,就很难判断。但更重要的是:

按下取消按钮时,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; }
        }
    }    
}
使用System.Threading.Tasks;
使用System.IO;
使用系统线程;
使用NUnit.Framework;
命名空间取消测试{
[测试夹具]
取消时的公共类{
[测试]
请求取消时取消循环时公共无效(){
var inputFile=newfileinfo(“somebigfile.txt”);
var outputFile=newfileinfo(“outputFile.txt”);
var cts=新的CancellationTokenSource();
cts.Cancel();
Assert.ThrowsAsync(()=>whileloop(inputFile、outputFile、cts.Token));
}
私有异步任务whileloop(FileInfo输入文件、FileInfo输出文件、CancellationToken令牌){
使用(var inputStream=inputFile.OpenRead())
使用(var outputStream=outputFile.OpenWrite()){
var buffer=新字节[1024*1024];
var totalReadBytesCount=0;
var readbytescont=0;
而((readBytesCount=await inputStream.ReadAsync(缓冲区,0,缓冲区,长度,标记))>0){
等待outputStream.WriteAsync(缓冲区,0,readBytesCount,令牌);
token.ThrowIfCancellationRequested();
totalReadBytesCount+=readBytesCount;
var progress=totalReadBytesCount*100.0/inputStream.Length;
vm.BusyContent=((int)progress.ToString();
}
}
}        
私有静态类vm{
公共静态字符串BusyContent{get;set;}
}
}    
}

希望这能让您走上正轨。

您可能会遇到一个与您如何设置代码有关的问题。如果不仔细检查每一行,就很难判断。但更重要的是:

按下取消按钮时,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; }
        }
    }    
}
使用System.Threading.Tasks;
使用System.IO;
使用系统线程;
使用NUnit.Framework;
命名空间取消测试{
[测试夹具]
取消时的公共类{
[测试]
请求取消时取消循环时公共无效(){
var inputFile=newfileinfo(“somebigfile.txt”);
var outputFile=newfileinfo(“outputFile.txt”);
var cts=新的CancellationTokenSource();
cts.Cancel();
Assert.ThrowsAsync(()=>whileloop(inputFile、outputFile、cts.Token));
}
私有异步任务whileloop(FileInfo输入文件、FileInfo输出文件、CancellationToken令牌){
使用(var inputStream=inputFile.OpenRead())
使用(var outputStream=outputFile.OpenWrite()){
var buffer=新字节[1024*1024];
var totalReadBytesCount=0;
var readbytescont=0;
而((readBytesCount=await inputStream.ReadAsync(缓冲区,0,缓冲区,长度,标记))>0){
等待outputStream.WriteAsync(缓冲区,0,readBytesCount,令牌);
token.ThrowIfCancellationRequested();
totalReadBytesCount+=readBytesCount;
var progress=totalReadBytesCount*100.0/inputStream.Length;
vm.BusyContent=((int)progress.ToString();
}
}
}        
私有静态类vm{
公共静态字符串BusyContent{get;set;}
}
}    
}

希望这能让你走上正轨。

你正在检查
令牌.IsCancellationRequested
并在执行
throwifcCancellationRequested()
之前退出循环。这是很多代码,你能发布一个吗?@svick我编辑我的问题以减少数量code@JSteward我已经编辑了代码,但问题仍然存在。有什么想法吗?你在检查