Subscribing to the LoadFileCommand
does not invoke the command. The command is not invoked until you call one of the execute methods on the command. In your case you want to call LoadFileCommand.ExecuteAsync
. This will return an IObservable<File>
in your case, I believe. Disposing of the subscription to that observable or otherwise terminating the observable will cause the observable to request that the cancellation token that was passed to LoadFile
in your delegate be cancelled.
I tried to create a .NET Fiddle here to demonstrate but it keeps saying an assembly is not referenced even though it clearly is. Anyway, here is the same code you can copy into LinqPad or a console application if you want to play around with it:
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"));
This is the output from the above code. You can see that the cancellation token does indeed request cancellation:
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
Edit - Possible Solution
So I think I have something that will work in your scenario while still allowing you to use the Xaml binding. I am pushing the cancellation logic into the command factory method rather trying to grab individual invocations and cancel those.
CancelOpenFileCommand = ReactiveCommand.Create();
LoadFileCommand =
ReactiveCommand
.CreateAsyncObservable(_ =>
Observable
.FromAsync(cancellationToken => LoadFile(cancellationToken))
.TakeUntil(CancelOpenFileCommand));
Now if you bind the button you want to use to open the file to the LoadFileCommand
and the button you want to use to cancel the command to the CancelOpenFileCommand
everything should just work.
Here is an example using the same pattern I describe above. I replaced LoadFile
with a dummy task that just contains a loop that loops five times, inside the loop I am writing the state of the cancellation token to the console and then delaying for one second. So the task should take five seconds to complete. But instead of allowing it to complete I am invoking CancelOpenFileCommand
after one second. This demonstrates that the cancellation token is being cancelled when the CancelOpenFileCommand
is invoked and that the command is terminating early.
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));
And here is the console output:
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