【问题标题】:ReactiveCommand.CreateAsync task. How do I cancel the task using a Button?ReactiveCommand.CreateAsync 任务。如何使用按钮取消任务?
【发布时间】:2016-02-28 11:09:30
【问题描述】:

我有这个 ReactiveCommand;

LoadFileCommand = ReactiveCommand.CreateAsyncTask((_, cancellationToken) => LoadFile(cancellationToken));

我也订阅了命令

subscription = LoadFileCommand.Subscribe(file => OnFileLoaded(file);

现在我想从 UI(在按钮中)使用另一个命令来取消任务。

但是怎么做呢?

我无法将我的取消令牌“注入”到 LoadFileCommand。我真的迷路了!

编辑:

目前,在我的 MainViewModel.cs 下(在构造函数中)我有这个:

OpenFileCommand = ReactiveCommand.CreateAsyncTask(async (o, ct) => await LoadFile(ct));

var whenButtonClick =
    Observable
        .Timer(TimeSpan.FromSeconds(10));
whenButtonClick.Subscribe(_ => Console.WriteLine());

OpenFileCommand
    .ExecuteAsync()
    .TakeUntil(whenButtonClick)
    .Subscribe(OnDocumentLoaded);

我的视图中有一个绑定到 LoadFileCommand 的“加载”按钮,但代码在创建视图模型后立即执行任务,而不是在用户单击按钮时执行。

顺便说一句,我想要另一个“取消”按钮,允许用户取消加载。

【问题讨论】:

  • 您能告诉我您将按钮绑定到OpenFileCommand 的位置吗?
  • 我正在使用 XAML 中的绑定来绑定命令,例如
  • 我认为您不会使用 XAML 绑定取得任何进展。这将依赖ICommand 接口来调用Execute,它返回void。您需要调用 ExecuteAsync 并挂在它返回的 observable 上,以便您可以取消它。我会尝试将一些可能适合您的情况放在一起。
  • 无赖!我希望你能为我提供一些解决方法来实现我所需要的。几乎没有关于取消方案的在线文档。感谢您的努力!
  • 我想我有一个可能的解决方案给你。看看我更新的答案。

标签: c# .net system.reactive reactive-programming reactiveui


【解决方案1】:

订阅LoadFileCommand 不会调用该命令。直到您调用命令的执行方法之一,才会调用该命令。在您的情况下,您想致电LoadFileCommand.ExecuteAsync。我相信,这将在您的情况下返回IObservable<File>。处理对该 observable 的订阅或以其他方式终止 observable 将导致 observable 请求取消在您的委托中传递给 LoadFile 的取消令牌。

我试图创建一个 .NET Fiddle here 来演示,但它一直说没有引用程序集,即使它显然是。无论如何,如果您想尝试一下,可以将相同的代码复制到 LinqPad 或控制台应用程序中:

var testCommand = ReactiveCommand.CreateAsyncTask(async (name, ct) =>
{
    // Do some long running work and periodically check if the
    // token has been cancelled.
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(
            "{0} cancellation requested: {1}", 
            name, 
            ct.IsCancellationRequested);

        if (ct.IsCancellationRequested)
        {
            ct.ThrowIfCancellationRequested();
        }
        await Task.Delay(1000);
    }
});

var whenButtonClick =
    Observable
    .Timer(TimeSpan.FromSeconds(2));

// Execute a command that is cancelled when a button click happens.
// Note the TakeUntil(whenButtonClick)
testCommand
.ExecuteAsync("first")
.TakeUntil(whenButtonClick)
.Subscribe(
    onNext: _ => Console.WriteLine("first next"),
    onCompleted: () => Console.WriteLine("first completed"));

// Execute a command that runs to completion.
testCommand
.ExecuteAsync("second")
.Subscribe(
    onNext: _ => Console.WriteLine("second next"),
    onCompleted: () => Console.WriteLine("second completed"));

这是上述代码的输出。您可以看到取消令牌确实请求取消:

第一次取消请求:错误
第二次取消请求:错误
第二次取消请求:错误
第一次取消请求:错误
首次完成
第一次取消请求:真
第二次取消请求:错误
第二次取消请求:错误
第二次取消请求:错误
第二个下一个
第二次完成

编辑 - 可能的解决方案

所以我认为我有一些东西可以在您的场景中工作,同时仍然允许您使用 Xaml 绑定。我将取消逻辑推送到命令工厂方法中,而不是尝试获取单个调用并取消它们。

CancelOpenFileCommand = ReactiveCommand.Create();

LoadFileCommand = 
    ReactiveCommand
    .CreateAsyncObservable(_ =>
        Observable
        .FromAsync(cancellationToken => LoadFile(cancellationToken))
        .TakeUntil(CancelOpenFileCommand));

现在,如果您将用于打开文件的按钮绑定到LoadFileCommand,并将用于取消命令的按钮绑定到CancelOpenFileCommand,一切都应该正常工作。

这是一个使用我上面描述的相同模式的示例。我用一个虚拟任务替换了LoadFile,它只包含一个循环五次的循环,在循环内我将取消令牌的状态写入控制台,然后延迟一秒钟。所以这个任务应该需要五秒钟才能完成。但是我没有让它完成,而是在一秒钟后调用CancelOpenFileCommand。这表明在调用 CancelOpenFileCommand 时取消令牌正在被取消,并且命令提前终止。

var CancelOpenFileCommand = ReactiveCommand.Create();

CancelOpenFileCommand
.Subscribe(x => 
    Console
    .WriteLine(
        "{0} CancelOpenFileCommand Invoked", 
        DateTime.Now.TimeOfDay));

var LoadFile = new Func<CancellationToken, Task>(async cancellationToken =>
    {
        for (int i = 0; i < 5; i++)
        {
            Console
            .WriteLine(
                "{0} Cancellation requested: {1}", 
                DateTime.Now.TimeOfDay, 
                cancellationToken.IsCancellationRequested);             

            if (cancellationToken.IsCancellationRequested)
            {
                cancellationToken.ThrowIfCancellationRequested();
            }
            await Task.Delay(1000);
        }
    });

var LoadFileCommand = 
    ReactiveCommand
    .CreateAsyncObservable(
        name =>
            Observable
            .FromAsync(ct => LoadFile(ct))
            .TakeUntil(CancelOpenFileCommand));

LoadFileCommand.Execute(null);

Observable
.Timer(TimeSpan.FromSeconds(1))
.Subscribe(_ => CancelOpenFileCommand.Execute(null));

这是控制台输出:

19:04:57.6087252 请求取消:错误
19:04:58.6157828 请求取消:错误
19:04:58.6197830 已调用 CancelOpenFileCommand
19:04:59.6268406 请求取消:真

【讨论】:

  • 那么做subscription = LoadFileCommand.Subscribe(file => OnFileLoaded(file) 错了吗?我以为订阅命令是Task完成后接收结果的方式。我错过了吗?什么?
  • @Super 不,这没有错。您会在 LoadFileCommand observable 上获得命令调用的结果。如果要取消命令,我知道的唯一方法是捕获调用命令时返回的 observable 并终止该 observable。我不知道有任何方法可以使用 observable 命令来做到这一点。哪种是有意义的,如果它是一个长时间运行的任务,该命令可能会被多次调用,那么取消单个命令调用的唯一方法是拥有一些代表该命令调用的对象。
  • 我有点困惑 :( 我正在做的是将一个按钮绑定到 LoadCommand。在 MainViewModel 中(在 ctor 中)。如果我把行 testCommand .ExecuteAsync("first") 它引发了任务即使未单击该按钮。我希望用户通过单击一个按钮来提升它并允许使用另一个按钮取消它。请查看我在原始消息中发布的编辑。
  • @SuperJMN 抱歉,我试图用我的代码展示的是一个示例,演示如何在命令被调用后取消它。我并不是建议可以将代码合并到您的项目中来解决您的问题。
猜你喜欢
  • 1970-01-01
  • 2017-10-30
  • 2013-03-12
  • 1970-01-01
  • 1970-01-01
  • 2016-12-27
  • 2020-01-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多