I have two simple Run/Cancel commands
Run = ReactiveCommand.CreateAsyncTask(x => RunImpl());
Run.ThrownExceptions.Subscribe(ex => UserError.Throw(ex.Message, ex));
Cancel = this.WhenAnyObservable(x => x.Run.IsExecuting).ToCommand();
Run.TakeUntil(Cancel);
Should I expect a cancellation token signaled into my async method when Cancel is executed?
private async Task<bool> RunImpl(CancellationToken cancellationToken = default(CancellationToken))
{
//some stuffs
if (cancellationToken.IsCancellationRequested)
return;
//next stuffs
}
If it´s so, what I'm doing wrong? Because IsCancellationRequest never goes true.
Thanks in advance.
Run.TakeUntil(Cancel);
This has no effect, as it only creates a new observable, which will never be used.
Run = ReactiveCommand.CreateAsyncTask(x => RunImpl());
This will provide a default (never used) cancellationToken, hence will never cancel.
I believe the correct way is:
Run = ReactiveCommand.CreateAsyncObservable(_ => Observable.StartAsync(RunImpl).TakeUntil(Cancel));
Cancel = this.WhenAnyObservable(x => x.Run.IsExecuting).ToCommand();
This way, the observable returned by TakeUntil will be suscribed by the command when executing, and TakeUntil will take care of canceling whenever the Cancel command runs.
Related
I am currently working on a project where I send UDP commands to a Tello drone.
The problem is that it uses UDP and when I send commands too fast before the previous one hasn't finished yet, the second command/action doesn't take place. I am using RxJS for this project and I want to create a mechanism to wait for the response ("ok" or "error") from the drone.
My Idea is to have 2 different observables. 1 observable that is the input stream from the responses from the drone and one observable of observables that I use as a commandQueue. This commandQueue has simple observables on it with 1 command I want to send. And I only want to send the next command when I received the "ok" message from the other observable. When I get the "ok" I would complete the simple command observable and it would automatically receive the next value on the commandQueue, being the next command.
My code works only when I send an array of commands, but I want to call the function multiple times, so sending them 1 by 1.
The following code is the function in question, testsubject is an observable to send the next command to the drone.
async send_command_with_return(msg) {
let parentobject = this;
let zeroTime = timestamp();
const now = () => numeral((timestamp() - zeroTime) / 10e3).format("0.0000");
const asyncTask = data =>
new Observable(obs => {
console.log(`${now()}: starting async task ${data}`);
parentobject.Client.pipe(take(1)).subscribe(
dataa => {
console.log("loool")
obs.next(data);
this.testSubject.next(data);
console.log(`${now()}: end of async task ${data}`);
obs.complete();
},
err => console.error("Observer got an error: " + err),
() => console.log("observer asynctask finished with " + data + "\n")
);
});
let p = this.commandQueue.pipe(concatMap(asyncTask)).toPromise(P); //commandQueue is a subject in the constructor
console.log("start filling queue with " + msg);
zeroTime = timestamp();
this.commandQueue.next(msg);
//["streamon", "streamoff", "height?", "temp?"].forEach(a => this.commandQueue.next(a));
await p;
// this.testSubject.next(msg);
}
streamon() {
this.send_command_with_return("streamon");
}
streamoff() {
this.send_command_with_return("streamoff");
}
get_speed() {
this.send_command_with_return("speed?");
}
get_battery() {
this.send_command_with_return("battery?");
}
}
let tello = new Tello();
tello.init();
tello.streamon();
tello.streamoff();
You can accomplish sending commands one at a time by using a simple subject to push commands through and those emissions through concatMap which will execute them one at a time.
Instead of trying to put all the logic in a single function, it will may be easier to make a simple class, maybe call it TelloService or something:
class TelloService {
private commandQueue$ = new Subject<Command>();
constructor(private telloClient: FakeTelloClient) {
this.commandQueue$
.pipe(
concatMap(command => this.telloClient.sendCommand(command))
)
.subscribe()
}
sendCommand(command: Command) {
this.commandQueue$.next(command);
}
}
When the service is instantiated, it subscribes to the commandQueue$ and for each command that is received, it will "do the work" of making your async call. concatMap is used to process commands one at a time.
Consumers would simply call service.sendCommand() to submit commands to the queue. Notice commands are submitted one at a time, it's not necessary to submit an array of commands.
Here is a working StackBlitz example.
To address your condition of waiting until you receive an ok or error response before continuing, you can use takeWhile(), this means it will not complete the observable until the condition is met.
To introduce a max wait time, you can use takeUntil() with timer() to end the stream if the timer emits:
this.commandQueue$
.pipe(
concatMap(command => this.telloClient.sendCommand(command).pipe(
takeWhile(status => !['ok', 'error'].includes(status), true),
takeUntil(timer(3000))
))
)
.subscribe()
Here's an updated StackBlitz.
I'm trying to make an API call for each element of an array, and emit an specific event if every API response is true.
I was doing the following:
let emit = true
array.forEach(element => {
this.service.getElement(element).subscribe(loaded => {
if(!loaded){
emit = false;
}
});
});
this.loaded.emit(emit);
But the last line always emits true
How can I wait for every request to be resolved before making the output event emission?
So the last line will always emit true because the service code is executed asynchronously. This means that it executes everything except the callback function in the subscribe method. This callback function executed once the stream emits a new element. I guess, you are making Http requests to an endpoint here. If so, the callback function is executed for each Http response and the order is not guaranteed.
What you can do is:
forkJoin(
...array.map(e => this.service.getElement(e))
).subscribe(responseArray =>
console.log(responseArray);
console.log('Complete');
);
The callback only executes if all Http response are available. And responseArray has the exact same order as the elements in the array.
Note: Keep in mind that if you do
let emit = false;
forkJoin(...).subscribe(() => emit = true);
console.log(emit);
It would print you false, because the callback executes async. If that seems like strange behaviour for you, I'd highly recommend to read about the JavaScript Eventloop.
Cause call http API is async, so this.loaded.emit(emit) executed first;
fix:
let emit = true
array.forEach(element => {
this.service.getElement(element).subscribe(loaded => {
if(!loaded){
emit = false;
this.loaded.emit(emit);
}
});
});
If you want execute this.loaded.emit(emit) when all of API response is true, try use forkJoin.
Actually I am using RxAndroid, Retrofit2.0 and Okhttp 3.2.0 in my project.
I am using Observable.merge to call multiple request in order to upload files.
Is there any solution to cancel a unique request ? I know I could use subscription.unsubscribe() but it will cancel all tasks.
I Used okhttp method like client.dispatcher.get(0).cancel() but it throw me an Io Exception like Canceled and it will cancel all tasks.
Do you have a proper way to cancel only a selected request ? Thank you
You could attach use a takeUntil(Observable a) to each of your constituent Observables and use the "control" observable to selective cancel them. For example
PublishSubject<Object> control1 = PublishSubject.create();
PublishSubject<Object> control2 = PublishSubject.create();
Observable<T> cancellableRestrofitObservable1 = retrofitObservable1.takeUntil(control1.asObservable());
Observable<T> cancellableRestrofitObservable2 = retrofitObservable2.takeUntil(control2.asObservable());
Observable<T> mergedObservable = Observable.merge(cancellableRestrofitObservable1,cancellableRestrofitObservable2);
// To cancel retrofitObservable1
control1.onNext("cancel");
It's working well. This is the code:
PublishSubject control = PublishSubject.create();
publishSubjectMap.put(position, control);
Observable<Response<Upload>> cancellableRestrofitObservable = dropboxapi.uploadImage(requestBody, params, position)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.takeUntil(control.asObservable());
observables.add(cancellableRestrofitObservable);
Observable<Response<Upload>> mergedObservable = Observable.merge(observables);
//To cancel
publishSubjectMap.get(event.getPosition()).onNext("cancel");
I have an activity which has an async method in it. This async method is long running. After the async method returns, the UI needs to be updated and some of the controls reference the activity.
At the moment, everything works correctly if you do not have a configuration change (like screen rotation) while the async task is running. However, if a configuration change happens while it is running, then the exception Activity is destroyed is thrown and the UI is not updated. From what reading I have done, this seems to be because the async method captures context and then tries to update the old context which is of course destroyed after the configuration change.
My question is: What are the best ways to solve this problem or at worst case scenario work around it?
I personally think you have only three options
You can disable rotation permanently or temporary, but this is a bad practice
To disable it permanently set ConfigurationChanges
[Activity(Label = "...", ConfigurationChanges = Android.Content.PM.ConfigChanges.KeyboardHidden | Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]
To disable it temporary while task working you should disable rotation handling,
disable
this.RequestedOrientation = Android.Content.PM.ScreenOrientation.Nosensor;
enable
this.RequestedOrientation = Android.Content.PM.ScreenOrientation.Sensor;
If you are using fragment you can prevent fragment destroy with RetainInstance = true. That might work, but i never tested it.
You can cancel task with CancelationToken and restart it in OnRestoreInstanceState()
Here is example how to cancel task
{
CancellationTokenSource cts;
...
// If a download process is already underway, cancel it.
if (cts != null)
{
cts.Cancel();
}
// Now set cts to cancel the current process if the button is chosen again.
CancellationTokenSource newCTS = new CancellationTokenSource();
cts = newCTS;
try
{
//Send cts.Token to carry the message if there is a cancellation request.
await AccessTheWebAsync(cts.Token);
}
// Catch cancellations separately.
catch (OperationCanceledException)
{
ResultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
ResultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
// When the process is complete, signal that another process can proceed.
if (cts == newCTS)
cts = null;
}
And in the task
async Task AccessTheWebAsync(CancellationToken ct)
{
...
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
// Check for cancellations before displaying information about the
// latest site.
ct.ThrowIfCancellationRequested();
...
}
There are plenty of things you could do, but please don't go and disable the phones ability to turn the screen -- that is just going to ignore your users.
At a highlevel you will have to do two things:
Make sure the async task keeps running and is not restarted if the activity dies.
You can solve that by moving the task either into the application class or (cleaner) into a headless fragment with setRetainInstance set to true.
In the onDestroy method in the activity, remove it from the async task, in the onCreate task give the activity to the async task (if it exist).
This is what prevents the async task from calling the old context and can be done with a simple java setter on the async task. Don't forget to cache the result in the task if the activity is currently not connected.
In the end what I ended up doing was encapsulating the async task in another class which held a reference to the current activity, which implemented and interface which defined a method which handles the async response and updates the UI.
The activity held a static variable of the encapsulated async task, and if it was running during a config change, the encapsulated async's task reference to the activity was updated to the new activity.
I'm using ThreadPool.QueueUserWorkItem for do an async task that do POST request via HTTP.
ThreadPool.QueueUserWorkItem(new WaitCallback(UploadPhoto), photoFileName);
For now I want to add possibility for a canceling upload from UI.
I have two questions:
How can I realize thread interruption?
Is ThreadPool suitable for my target?
Consider using Task.Factory.StartNew to do async work on WP7. You can use CancellationTokens to force a cancellation. This is how I do my async work. To realize an interruption, you can do the following (using Tasks):
var task = Task.Factory.StartNew( ( )=>
{
// some operation that will be cancelled
return "some value";
})
.ContinueWith( result =>
{
if(result.Status == TaskStatus.Cancelled) // you have other options here too
{
// handle the cancel
}
else
{
string val = result.Result; // will be "some value";
}
});
The ContinueWith clause chains another method to occur after the body of the first task completes (one way or another). The parameter 'result' for the ContinueWith method is the Task that the ContinueWith is chained to, and there is a property called Result on the task 'result' that is whatever return value is supplied by the preceding task.