ReactiveCommand.CreateAsync 任务。如何使用按钮取消任务?
ReactiveCommand.CreateAsync task. How do I cancel the task using a Button?
我有这个 ReactiveCommand;
LoadFileCommand = ReactiveCommand.CreateAsyncTask((_, cancellationToken) => LoadFile(cancellationToken));
我也订阅了命令
subscription = LoadFileCommand.Subscribe(file => OnFileLoaded(file);
现在我想使用 UI(在按钮中)使用另一个命令来取消任务。
但是怎么做?
我无法 "inject" 我的 cancellationToken 到 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 的 "Load" 按钮,但是代码会在创建视图模型后立即执行任务,而不是在用户单击按钮时执行。
顺便说一下,我想要另一个 "Cancel" 按钮,允许用户取消加载。
订阅 LoadFileCommand
不会调用该命令。在您调用命令的执行方法之一之前,不会调用该命令。在你的情况下,你想调用 LoadFileCommand.ExecuteAsync
。我相信,在您的情况下,这将是 return 和 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"));
这是上述代码的输出。可以看到取消令牌确实请求取消:
first cancellation requested: False
second cancellation requested: False
second cancellation requested: False
first cancellation requested: False
first completed
first cancellation requested: True
second cancellation requested: False
second cancellation requested: False
second cancellation requested: False
second next
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 Cancellation requested: False
19:04:58.6157828 Cancellation requested: False
19:04:58.6197830 CancelOpenFileCommand Invoked
19:04:59.6268406 Cancellation requested: True
我有这个 ReactiveCommand;
LoadFileCommand = ReactiveCommand.CreateAsyncTask((_, cancellationToken) => LoadFile(cancellationToken));
我也订阅了命令
subscription = LoadFileCommand.Subscribe(file => OnFileLoaded(file);
现在我想使用 UI(在按钮中)使用另一个命令来取消任务。
但是怎么做?
我无法 "inject" 我的 cancellationToken 到 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 的 "Load" 按钮,但是代码会在创建视图模型后立即执行任务,而不是在用户单击按钮时执行。
顺便说一下,我想要另一个 "Cancel" 按钮,允许用户取消加载。
订阅 LoadFileCommand
不会调用该命令。在您调用命令的执行方法之一之前,不会调用该命令。在你的情况下,你想调用 LoadFileCommand.ExecuteAsync
。我相信,在您的情况下,这将是 return 和 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"));
这是上述代码的输出。可以看到取消令牌确实请求取消:
first cancellation requested: False
second cancellation requested: False
second cancellation requested: False
first cancellation requested: False
first completed
first cancellation requested: True
second cancellation requested: False
second cancellation requested: False
second cancellation requested: False
second next
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 Cancellation requested: False
19:04:58.6157828 Cancellation requested: False
19:04:58.6197830 CancelOpenFileCommand Invoked
19:04:59.6268406 Cancellation requested: True