Cancelling and re-executing ReactiveCommand - c#

I'm struggling with a ReactiveUI use case that I feel is so simple there must be "out-of-the-box" support for it. But I cannot find it.
The scenario is a basic search interface with these features:
A search string TextBox where the user enters the search text
A result TextBox where the result is presented
An indicator showing that a search is in progress
The search should work like this:
The search string TextBox is throttled, so that after 500ms of
inactivity, a search operation is initiated.
Each time a new search is initiated any ongoing search operation should be cancelled.
Basically I'm trying to extend the "Compelling example" to cancel the currently executing command before starting a new command.
Seems easy enough? Yeah, but I cannot get it right using ReactiveCommand. This is what I have:
var searchTrigger = this.WhenAnyValue(vm => vm.SearchString)
.Throttle(TimeSpan.FromMilliseconds(500))
.Publish().RefCount();
var searchCmd = ReactiveCommand.CreateFromObservable(
() => Observable
.StartAsync(ct => CancellableSearch(SearchString, ct))
.TakeUntil(searchTrigger));
searchCmd.ToPropertyEx(this, vm => vm.Result);
searchCmd.IsExecuting.ToPropertyEx(this, vm => vm.IsSearching);
searchTrigger.Subscribe(_ => searchCmd.Execute(Unit.Default).Subscribe());
The above code works in all aspects except searchCmd.IsExecuting. I kick off a new search regardless of the state of searchCmd.CanExecute. This makes IsExecuting unreliable since it assumes serial operation of the commands. And I cannot use InvokeCommand instead of Execute since then new searches would not be started while a search is in progress.
I currently have a working solution without ReactiveCommand. But I have a strong feeling this simple use case should be supported in a straightforward way using ReactiveCommand. What am i missing?

AFAICT Rx7 doesn't really handle this kind of overlapping execution. All the messages will eventually make it through but not in a way that will keep your IsExecuting consistently true. Rx6 used an In flight counter so overlapping executions were handled but Rx7 simplified it all way down. Most likely for performance and reliability (but I'm just guessing). Because Tasks aren't going to cancel right away that first command is going to complete after the second command starts which leads to IsExecuting toggling from true to false to true to false. But that middle transition from false to true to false happens instantly as the messages catch up. I know you said you had a non Reactive Command working but here's a version that I think works with Reactive Commands by waiting for the first command to finish or finish cancelling. One advantage to waiting until the Task actually cancels is that you are assured you don't have two hands in the cookie jar :-) Which might not matter in your case but can be nice in some cases.
//Fires an event right away so search is cancelled faster
var searchEntered = this.WhenAnyValue(vm => vm.SearchString)
.Where(x => !String.IsNullOrWhiteSpace(x))
.Publish()
.RefCount();
ReactiveCommand<string, string> searchCmd = ReactiveCommand.CreateFromObservable<string, string>(
(searchString) => Observable.StartAsync(ct => CancellableSearch(SearchString, ct))
.TakeUntil(searchEntered));
//if triggered wait for IsExecuting to transition back to false before firing command again
var searchTrigger =
searchEntered
.Throttle(TimeSpan.FromMilliseconds(500))
.Select(searchString => searchCmd.IsExecuting.Where(e => !e).Take(1).Select(_ => searchString))
.Publish()
.RefCount();
_IsSearching =
searchCmd.IsExecuting
.ToProperty(this, vm => vm.IsSearching);
searchTrigger
.Switch()
.InvokeCommand(searchCmd);

Related

In ReactiveUI the same code behaves differently depending on how observable is subscribed

The code:
StartOperation = ReactiveCommand.CreateFromObservable(() => Observable.Start(() =>
{
SelectedOperation.State = OperationState.ACTIVE.ToString();
checkboxService.SaveChanges();
}));
results in binding handlers in being invoked seemingly randomly, whereas:
StartOperation = ReactiveCommand.Create(() =>
{
Interactions.Confirm.Handle(new MessageBoxArguments { CanCancel = false, IsWarning = false, Message = "Operation started!", Title = "Success" }).Subscribe(x =>
{
SelectedOperation.State = OperationState.ACTIVE.ToString();
checkboxService.SaveChanges();
});
});
Works perfectly. I assume this is to due with scheduling, but I would have thought that in both cases the code would be scheduled to run in the same way, except in the second example, a message box is shown first. Maybe the delay makes things work correctly?
The project is Winforms (I know) on .Net Standard and ReactiveUI is 17.1.50.
I would be very happy for some pointers here!
So it turns out that the reason it worked in the second example was not because of scheduling, but rather because showing the MessageBox, forced the UI to update events.
The reason this is needed, is because the button triggering StartOperation, is also affected by SelectedOperation.State being updated (the Enabled property is dependent on this) and it seems the flow is like this:
Clicking triggers StartOperation (OnClick)
This then updates SelectedOperation.State
This triggers various bindings, finally leading to an update of the Enabled property
This is not working well/predictably, because the button is in some kind of transition state firing events and so on.
The solution in short, is to make sure that Application.DoEvents() is called somewhere in that chain, forcing the button out of whatever state it's in.

c# make my a specific line to timeout after x seconds c#

I have a line in C# which does not work very reliable and does not time out at all and runs for infinity.
to be more precise i am trying to check the connection to a proxy WebClient.DownloadString
I want it to timeout after 5 seconds without making the full method asynchronous
so the code should be like this:
bool success = false
do_this_for_maximum_5_seconds_or_until_we_reach_the_end
{
WebClient.DownloadString("testurl");
success = true;
}
it will try to download testurl and after it did download it it will set success to true. If DownloadString takes more than 5 seconds, the call is canceled, we do not reach the the line where we set success to true, so it remains false and i know that it field.
The thread will remain frozen while we try to DownloadString, so the action is not taking parallel. The ONLY difference to a normal line would be that we set a timeout after 5 seconds
Please do not suggest alternatives such as using HttpClient, because i need a similar codes also for other places, so i simply want a code which will run in a synchronous application (i have not learned anything about asynchronus programing therefore i would like to avoid it completely)
my approach was like suggested by Andrew Arnott in this thread
Asynchronously wait for Task<T> to complete with timeout
however my issue is, I am not exactly sure what type of variable "SomeOperationAsync()" is in his example (i mean it seems like a task, but how can i put actions into the task?), and the bigger issue is that VS wants to switch the complete Method to asynchronos, but i want to run everything synchronous but just with a timeout for a specific line of code.
In case the question has been answered somewhere kindly provide a link
Thank you for any help!!
You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:
var downloadString =
Observable
.Using(() => new WebClient(), wc => Observable.Start(() => wc.DownloadString("testurl")))
.Select(x => new { success = true, result = x });
var timeout =
Observable
.Timer(TimeSpan.FromSeconds(5.0))
.Select(x => new { success = false, result = (string)null });
var operation = Observable.Amb(downloadString, timeout);
var output = await operation;
if (output.success)
{
Console.WriteLine(output.result);
}
The first observable downloads your string. The second sets up a timeout. The third, uses the Amb operator to get the result from which ever of the two input observables completes first.
Then we can await the third observable to get its value. And then it's a simple task to check what result you got.

Retry loop observable

In my chat client application, I have a Subject which gets OnNext-ed when a connection to the server drops OR a connection attempt fails. There is a subscription that when gets notified makes an attempt at a connection.
The way my chat application is and the way server connection works it sometimes allows multiple notifications around the same time, meaning while the subscription is processing one Job, it gets another in the queue which NEEDS TO BE IGNORED.
I am using an external local thread-safe variable to let my subscription know if it should ignore any other notifications.
reconnectSubject
.Where(x => this.IsRetryInProcess == false)
.Select(x =>
{
this.SetRetryInProcessThreadSafe(true);
var result = MakeConnectionAttemptAndReturnResult();
this.SetRetryInProcessThreadSafe(false);
return result;
)
.Where(x => x.IsSuccessful == false)
.Subscribe(x => reconnectSubject.OnNext(Unit.Default));
This works but it lacks keeping things coherent or encapsulation. I have mixed together the two worlds there. Is there any Operator in Observable that can help me achieve this without a external flag? May be some sort of an observable that requires ACKs?

.NET Stateless a parallel workflow / long parallel background process

I have a stateMachine where I need to download a huge amount of data in the background at State.LoadingOrderInfos. While my application is downloading the stuff (in the background), the operator should work on and go through the next states.
At State.InsertPcbs I need the downloaded data.
In short words. I want to call LoadECMDataAsync when entering State.LoadingOrderInfos and it should not disturb the normal workflow.
_machine.Configure(State.LoadingOrderInfos)
.Ignore(Trigger.WtPresent)
.Ignore(Trigger.WtNotPresent)
.SubstateOf(State.CanLogOut)
.Permit(Trigger.OrderLoadingFailed, State.OrderNotSelected)
.Permit(Trigger.OrderLoadingComplete, State.OrderCheckSetup)
.OnEntry(() =>
{
IsLoading = true;
Wt = null;
})
// Is this the way how to do it?
.OnEntry(() =>
{
LoadECMDataAsync();
})
.OnActivate(async () =>
{
if (await _LoadOrderInfos().ConfigureAwait(true))
{
_machine.Fire(Trigger.OrderLoadingComplete);
}
else
{
_machine.Fire(Trigger.OrderLoadingFailed);
}
})
.OnExit(() => IsLoading = false);
I think I would create a service class that fetches the data you need, maybe you could use Task.Run int this class to start the process.
When it's done, it could set a flag that is checkable. Or you could make it so that it calls a callback.
Then use PermitIf to check if a flag has been set.
Your problem is ortogonal, so I think it makes sense to split up the responsibilities.
BTW, you are using OnActivate. This should only be used if you need to "Activate" the state machine, like if you reload it from storage. At the moment it works like OnEntry (so use that), but I'd like to change it...

How to execute a command only once using ReactiveUI in Xamarin.Forms?

Using RxUI for Xamarin.Forms, how would you create a command that is meant to be executed only once automatically (when a page initially appears) but that te user can request its execution later on (like from a pull to refresh kind of event)?
I've hooked my command to the Appearing event using the FromEventPattern but when I navigate back to the page it gets executed again which is an undesired behavior.
This is my scenario: I need a list to be populated automatically when the user opens the page that contains it. Then the user can select an element and view its details in a separate page (using a NavigationPage), but when the user returns to the list page it gets repopulated which should not happen. The user should be able to request new data vía a button or pull to refresh, though.
Thank you.
Using Dorus hint: in your page consturctor you need to create an observable from the event, then Take just the first one:
Observable.FromEventPattern(ev => Appearing += ev, ev => Appearing -= ev)
.Select(e => Unit.Default)
.Take(1)
.InvokeCommand(ViewModel.InitialCollectionLoad);
Here's how I've handled this scenario. You'll need a behavior similar to this one, which will invoke your command only when a specific, known value is set.
// Only invoke the command when the property matches a known value that can't happen through normal execution
this.WhenAnyValue(vm => vm.SomeProperty)
.Where(sp => sp == null)
.Throttle(TimeSpan.FromSeconds(.25), TaskPoolScheduler.Default)
.Do(_ => Debug.WriteLine($"Refresh the List"))
.InvokeCommand(GetList)
.DisposeWith(SubscriptionDisposables);
At the end of your constructor, set SomeProperty to match the known value
this.SomeProperty = null; // or some value that makes sense
In this case, you don't need to fire the command manually in OnAppearing - it happens when your ViewModel is constructed for the first time and will not be executed again until the ViewModel is disposed and recreated.
It seems a little hack-ey to me, so I'm hoping wiser, more seasoned RxUI wizards will chime in, but it gets the job done.
If you prefer to leave the OnAppearing call in place, you could also potentially handle this by setting the canExecute property of your ReactiveCommand and using an entirely different ReactiveCommand for your PullToRefresh action (even though both commands would behave the same otherwise). In this scenario, you'd want to make the canExecute always false after the list is populated initially, so even when the user return to the page the initial population isn't triggered.
var isInitialized = this.WhenAnyValue(vm => vm.IsInit).Select( _ => _ == false).DistinctUntilChanged();
InitList = ReactiveCommand.CreateFromTask( _ =>
{
// get list
}, isInitialized);
RefreshList = ReactiveCommand.CreateFromTask( _ =>
{
// essentially the same as InitList, but with different/no canExecute parameters
});
InitList.ObserveOn(RxApp.MainThreadScheduler).Subscribe(result =>
{
this.IsInit = false
}).DisposeWith(SubscriptionDisposables);
The disadvantage here is, obviously, you have some logic duplicated
Maybe I'm misunderstanding, but I do this often, so I don't think I am.
I just use the VM creation as the trigger for the initial execution of the command. Then I associate that same command with the pull-to-refresh feature in XF.
So my VM looks like this:
public class MyVM
{
public MYVM()
{
this.refreshCommand = ...;
this
.refreshCommand
.Execute()
.Subscribe()
_ => {},
_ => {});
}
public ReactiveCommand<...> RefreshCommand => this.refreshCommand;
}
This way the command is executed as soon as the VM is created, so data is retrieved ASAP. But it won't re-execute again unless the VM is recreated or the user pulls-to-refresh.

Categories