What is the difference between using RxSwift's MainSchedule.instance and MainSchedule.asyncInstance within the context of observeOn?
asyncInstance guarantees asynchronous delivery of events whereas instance can deliver events synchronously if it’s already on the main thread.
As for why you would ever need to force asynchronous delivery when you’re already on the main thread: it’s fairly rare and I would typically try to avoid it, but sometimes you have a recursive reactive pipeline where one event triggers the delivery of a new event in the same pipeline. If this happens synchronously, it breaks the Rx contract and RxSwift will spit out a warning that you tried to deliver a second event before the first event finished. In this case you can observe on MainScheduler.asyncInstance to break the cycle.
Related
As we know, dart is a single-threaded language. So according to the document, we can use Futrure/Stream to implement a async opetation. It sends the time-consuming operation to the Event Queue.
What confused me is where the Event Queue working on. It is working on the dart threat? if yes, it will block the app.
Another question is Event Queue a FIFO queue. If i have two opertion, one is a 1mins needed networking request, the other is a click event. The two operation will send to the Event Queue.
So if the click event will blocked by the networking request? Because the queue is a FIFO queue?
So where is the event queue working on?
Thank you very much!
One thing to note is that asynchronous and multithreading are two different things. Dart uses Futures and async/await to achieve asynchronicity, but Dart is still inherently a single-threaded language.
The way it works is when a Future is created (either manually or via calling an async method), that process is added to an event queue, as you read. Then, in the middle of all the synchronous execution, whenever there is a lull, the event queue can take priority. It can then go through the processes and figure out if any of the Futures have been completed. If so, the result is passed along to any other asynchronous processes that are waiting on that resource, if any.
This also means that, yes, if your program hangs in the middle of an asynchronous operation (with the easy example of an endless loop via while (true) {}), it will freeze the entire program, including the synchronous code and other asynchronous processes still waiting to resolve (even if the conditions allowing them to resolve have already occurred).
However, in your case, this won't be an issue. If you fire an asynchronous process in the form of a network request followed by another in the form of a "click event" (not sure what you're referring to, but I'll assume it's asynchronous as well), they will both be added to the event queue in that order. But if the click event resolves before the network request, the event queue will merely recognize that the network request Future has not yet resolved and will move on to the click event that has.
As a side note, it's worth noting that Dart does have a multi-threading capability, albeit in a fairly roundabout way. Dart has something called an Isolate, which isn't a thread but a completely separate child program. This means that the Isolate cannot access any of the same data in memory as the root program itself. However, data can be passed between the two using SendPorts and ReceivePorts. This makes using Isolates slightly more complicated than threads, but it also means that, if no memory is shared, it virtually eliminates race conditions based on which thread accesses the memory first.
I might have the wrong idea of Isolate and Future. Please help me to clear it up. Here is my understanding of both subjects.
Isolate: Isolates run code in its own event loop, and each event may run smaller tasks in a nested microtask queue.
Future: A Future is used to represent a potential value, or error, that will be available at some time in the future.
My confusions are:
The doc says Isolate has it own loop? I feel like having its own event queue makes more sense to me, am I wrong?
Is future running asynchronously on the main Isolate? I'm assuming future task actually got placed at the end of event queue so if it will be execute by loop in the future. Correct me if I'm wrong.
Why use Isolate when there is future? I saw some examples using Isolate for some heavy task instead of Future. But why? It only makes sense to me when future execute asynchronously on the main isolate queue.
A Future is a handle that allows you to get notified when async execution is completed.
Async execution uses the event queue and code is executed concurrently within the same thread.
https://webdev.dartlang.org/articles/performance/event-loop
Dart code is by default executed in the root isolate.
You can start up additional isolates that usually run on another thread.
An isolate can be either loaded from the same Dart code the root isolate was started with (with a different entry-point than main() https://api.dartlang.org/stable/2.0.0/dart-isolate/Isolate/spawn.html) or with different Dart code (loaded from some Dart file or URL https://api.dartlang.org/stable/2.0.0/dart-isolate/Isolate/spawnUri.html).
Isolates don't share any state and can only communicate using message passing (SendPort/ReceivePort). Each isolate has its own event queue.
https://webdev.dartlang.org/articles/performance/event-loop
An Isolate runs Dart code on a single thread. Synchronous code like
print('hello');
is run immediately and can't be interrupted.
An Isolate also has an Event Loop that it uses to schedule asynchronous tasks on. Asynchronous doesn't mean that these tasks are run on a separate thread. They are still run on the same thread. Asynchronous just means that they are scheduled for later.
The Event Loop runs the tasks that are scheduled in what is called an Event Queue. You can put a task in the Event Queue by creating a future like this:
Future(() => print(hello));
The print(hello) task will get run when the other tasks ahead of it in the Event Queue have finished. All of this is happening on the same thread, that is, the same Isolate.
Some tasks don't get added to the Event Queue right away, for example
Future.delayed(Duration(seconds: 1), () => print('hello'));
which only gets added to the queue after a delay of one second.
So far everything I've been talking about gets done on the same thread, the same Isolate. Some work may actually get done on a different thread, though, like IO operations. The underlying framework takes care of that. If something expensive like reading from disk were done on the main Isolate thread then it would block the app until it finished. When the IO operation finishes the future completes and the update with the result is added to the Event Queue.
When you need to do CPU intensive operations yourself, you should run them on another isolate so that it doesn't cause jank in your app. The compute property is good for this. You still use a future, but this time the future is returning the result from a different Isolate.
Further study
Futures - Isolates - Event Loop
Dart asynchronous programming: Isolates and event loops
Are Futures in Dart threads?
The Event Loop and Dart
Flutter/Dart non-blocking demystify
The Engine architecture
Single Thread Dart, What? — Part 1
Single Thread Dart, What? — Part 2
Flutter Threading: Isolates, Future, Async And Await
The Fundamentals of Zones, Microtasks and Event Loops in the Dart Programming Language
An introduction to the dart:io library
What thread / isolate does flutter run IO operations on?
In one sentence we could say,
Isolates: Dart is single-threaded but it is capable of doing multi-threading stuff using Isolates (many processes).
Future: Future is a result which is returned when dart has finished an asynchronous work. The work is generally done in that single-thread.
Isolate could be compared to Thread even if dart is not multithreaded. It has it's own memory and event loop indeed, when Futures shares the same memory
Dart is able to spawn standalone processes, called Isolates (web workers in dart2js), which do not share memory when the main program, but are able to asynchronously, in another process (effectively a thread of sorts) is able to do computations without blocking the main thread.
A Future is run inside the Isolate that called it, not necesserally the main isolate.
I recommend this article which has better explanation than me.
TLDR: https://medium.com/flutter-community/isolates-in-flutter-a0dd7a18b7f6
Let's understand async-await first and then go into isolates.
void main() async {
// Read some data.
final fileData = await _readFileAsync();
final jsonData = jsonDecode(fileData);
// Use that data.
print('Number of JSON keys: ${jsonData.length}');
}
Future<String> _readFileAsync() async {
final file = File(filename);
final contents = await file.readAsString();
return contents.trim();
}
We want to read some data from a file and then decode that JSON and print the JSON Keys length. We don’t need to go into the implementation details here but can take the help of the image below to understand how it works.
When we click on this button Place Bid, it sends a request to _readFileAsync, all of which is dart code that we wrote. But this function _readFileAsync, executes code using Dart Virtual Machine/OS to perform the I/O operation which in itself is a different thread, the I/O thread.
This means, the code in the main function runs inside the main isolate. When the code reaches the _readFileAsync, it transfers the code execution to I/O thread and the Main Isolate waits until the code is completely executed or an error occurs. This is what await keyword does.
Now, once the contents of the files are read, the control returns back to the main isolate and we start parsing the String data as JSON and print the number of keys. This is pretty straight forward. But let’s suppose, the JSON parsing was a very big operation, considering a very huge JSON and we start manipulating the data to conform to our needs. Then this work is happening on the Main Isolate. At this point of time, the UI could hang, making our users fustrated.
Now let's get back to isolates.
Dart uses Isolate model for concurrency. Isolate is nothing but a wrapper around thread. But threads, by definition, can share memory which might be easy for the developer but makes code prone to race conditions and locks. Isolates on the other hand cannot share memory and instead rely on message passing mechanism to talk with each other.
Using isolates, Dart code can perform multiple independent tasks at once, using additional cores if they’re available. Each Isolate has its own memory and a single thread running an event loop.
Hope this help solve someone's doubt.
Some of my observations on researching about flow framework are:
#Signal starts executing in the decider replay once the signal is received. #Signal method is executed in all the future replays of the same workflow. (Once signal is received, on each replay the decider executes #Signal). #Asynchronus methods should not be long running tasks, as decider will schedule Activity Tasks only after all #Asynch methods have completed execution.
Are my observations correct ? If yes: then what if in the same workflow I want a signal, which performs some task and then stop executing for future replays. Such as a pause signal: user might pause and resume a workflow multiple times.
Another problem is: How are the following types of cases handled by flow: A decider times out, and meanwhile two events come: Cancel workflow and Activity Completed. How does decider figure out that they are related and if cancellation is done, then do not responds to ActivityComplatedEvent.
It helps to not think about workflow behavior in terms of replay. Replay is just a mechanism for recovering workflow state. But when workflow logic is written it is not really visible besides the requirement of determinism and that workflow code is asynchronous and non blocking. So never think about replay when designing your workflow logic. Write it as it is a locally executing asynchronous program.
So when replay is not in a way the #Signal is just a callback method that executes once per received signal. So if you invoke some action from the #Signal method then it is going to execute once.
As for second question it depends on the order in which cancellation and activity completion are received. If cancellation is first then cancellation is delivered to a workflow first which might cause the cancellation of the activity. Cancellation is actually blocking waiting for activity to cancel. Completion of activity (which is the next event) unblocks the cancellation for it. If completion is second then activity completes and whatever follows it is cancelled by the next event. In majority of cases the result is exactly the same that activity completion is received but all logic after that is cancelled.
I have a selector method which performs search for particular text in two different libraries in two different threads called using dispatch_async.
Now this selector is bound to a textfield and as soon some characters change we can query the libraries for the text.
Now a search takes some time say like 0.3 - 0.4 second and if the first search is not complete before another character is entered I woould like to cancel the search and re-start with new characters in the text field.
So does calling cancelPreviousPerformRequestsWithTarget on the selector cancel the internal threads and libraries call...?
No. cancelPreviousPerformRequestsWithTarget has nothing to do with blocks dispatched via GCD (i.e. dispatch_async). It cancels previous invocations of
selectors scheduled for a later time on a specific NSRunLoop using -performSelector:withObject:afterDelay:. Furthermore, it can't cancel those invocations if they're already in progress, it can only prevent them from starting, if they're still waiting to begin.
There is no means by which to (safely) forcibly cancel in flight operations, regardless of the method used to dispatch them. The operation has to, itself, support cancel-ability, usually by checking a flag periodically during its work, and returning early if the flag says the operation should cancel.
Because someone will inevitably come along and say that NSOperation supports cancelation, I might as well get it out of the way now, by pointing out that NSOperation's cancelation support still requires the operation being canceled to periodically check a flag and intentionally return early, it's just that NSOperation has the cancelled property which provides the flag for you. For that to be useful to you, your code has to know that it's executing as part of an NSOperation, and it has to have a pointer to the specific NSOperation it's executing as part of, and it still has to periodically check the cancelled property of that NSOperation and return early in order to "support cancellation."
There is no free lunch for cancellation on non-garbage-collected runtimes.
I am trying to re-schedule queued block that will handle the update operations.
Main goal is updating UI objects (online user table...) with minimum amount of (UI update request). (Server sometimes rain down massive amount of updates, yay!)
For simplicity main scenario is;
The dispatch_queue_t instance (queue that will handle given UI updating block) is a serial dispatch queue (private dispatch queue)
The operation (UI updating block) is scheduled with dispatch_after with t amount of time (Instead of updating for each data set update, collect update requests within t amount of time and perform a single UI update for them)
In case our data set updated, check if there already exist a scheduled event. If yes, unschedule it from dispatch_queue_t instance. Then re-schedule same block with t amount of time delay.
Also;
t is a small amount of time interval that possibly won't be noticed by the user (like 500 ms.)
Any alternative approach is welcome.
My motive behind this;
i applied same logic via Android's Handler (post & removeCallbacks combination with Runnable instance) and i hope i could achieve the same on iOS.
Edit:
As #Sven suggested usage of NSOperationQueue is more suitable for the scenario as they support cancelling each NSOperation. I skimmed through documents and found;
Canceling Operations
Once added to an operation queue, an operation object is effectively owned by the queue and cannot be removed. The only way to dequeue an operation is to cancel it. You can cancel a single individual operation object by calling its cancel method or you can cancel all of the operation objects in a queue by calling the cancelAllOperations method of the queue object.
You should cancel operations only when you are sure you no longer need them. Issuing a cancel command puts the operation object into the “canceled” state, which prevents it from ever being run. Because a canceled operation is still considered to be “finished”, objects that are dependent on it receive the appropriate KVO notifications to clear that dependency. Thus, it is more common to cancel all queued operations in response to some significant event, like the application quitting or the user specifically requesting the cancellation, rather than cancel operations selectively.
This can easily be done with GCD as well, no need to reach for the big hammer that is NSOperationQueue here.
Just use a non-repeating dispatch timer source directly instead of dispatch_after (which is just a convenience wrapper around such a timer source, it doesn't actually enqueue the block onto the queue until the timer goes off).
You can reschedule a pending timer source execution with dispatch_source_set_timer().
You cannot remove or otherwise change an operation enqueued on a dispatch queue. Try using the higher level NSOperationQueue instead which supports cancellation.