I call an API and perform some actions based on the response.
let test = apiPublisher
.subscribe(...)
.receive(...)
.share()
test
.sink {
//do task1
}.store(...)
test
.sink {
//do task2
}.store(...)
test
.sink {
//do task3
}.store(...)
Now how can I execute the task1, task2, task3 one after another. I know I can have all the code in one sink block. For code readability I'm using the share() operator.
Code put in sinks need to be independent. If you want them to depend on each other (one should not start until the other finishes) then you can't put them in sinks.
You will have to put each task in its own Publisher. That way the system will know when each is finished and you can concat them.
test.task1
.append(test.task2)
.append(test.task3)
.sink { }
.store(...)
I'm assuming that each task needs something from test in order to perform its side effect. Also each task needs to emit a Void event before completing.
Related
The new Async/Await syntax looks great! but I wonder how to implement my own asynchronous implementation.
I've stumbled upon this API:
https://developer.apple.com/documentation/swift/task/3862702-suspend (overview in yield)
https://developer.apple.com/documentation/swift/task/3814840-yield (renamed to suspend)
This API allows me to suspend a task manually whenever I choose. The problem is, I'm am not sure how SHOULD I do it, in order to benefit from concurrency AND not avoid bad practices.
In other word, I don't know the best practices of Task.suspend()
for example:
func example() async {
for i in 0..<100 {
print("example", i)
await Task.suspend() // <-- is this OK?
}
}
Some specific questions:
how often should one call on suspend?
should suspend be called before an intensive operation, or after? (for example: IO, Crypto, etc...)
should there be a maximum amount of calls to suspend?
what is the "price" of calling suspend intensively?
when should one NOT call suspend?
are there any other ways to implement this kind of concurrency (async/await style, not GCD)
Real life example, I'm implementing a function that encrypts the content of a big file, since it is an IO+Crypto intensive task it should be async, I wonder how to use Task.suspend (or any other async/await tools) to make it asynchronous.
Calling Task.suspend() will suspend the current task for a few milliseconds in order to give some time to any tasks that might be waiting, which is particularly important if you’re doing intensive work in a loop and all your tasks use the same priority. Otherwise your heavy task can stop all asynchronous code in your app. For instance:
func f() async {
for _ in 0...10 {
var arr = (1...10000).map {_ in arc4random()}
arr.sort()
}
print("f")
}
func z() async {
print("z")
}
// Run in parallel
Task {
await f()
}
Task {
await z()
}
Outputs:
f
z
As you can see z() waits for f() because it does long-running operation of sorting a large array many times. To fix this you can add Task.suspend() in your loop:
func f() async {
for _ in 0...10 {
var arr = (1...10000).map {_ in arc4random()}
arr.sort()
await Task.suspend() // Voluntarily suspend itself
}
print("f")
}
Outputs:
z
f
async/await works on its own cooperative concurrent queues and if you don't want to do suspending consider moving your task to non-default priority(queue) e.g. Task(priority: .background) or run your heavy task on your separate queue.
The below code is used to execute a long running calculation on a background thread:
enum CalculationInterface {
private static var latestKey: AnyObject? // Used to cancel previous calculations when a new one is initiated.
static func output(from input: Input, return: #escaping (Output?) -> ()) {
self.latestKey = EmptyObject()
let key = self.latestKey! // Made to enable capturing `self.latestKey's` value.
DispatchQueue.global().async {
do {
let output = try calculateOutput(from: input, shouldContinue: { key === self.latestKey }) // Function cancels by throwing an error.
DispatchQueue.main.async { if (key === self.latestKey) { `return`(output) } }
} catch {}
}
}
}
This function is called from the main thread like so:
/// Initiates calculation of the output and sets it to the result when finished.
private func recalculateOutput() {
self.output = .calculating // Triggers calculation in-progress animation for user.
CalculationInterface.output(from: input) { self.output = $0 } // Ends animation once set and displays calculated output to user.
}
I'm wondering if it's possible for the closure that's pushed to DispatchQueue.main to execute while the main thread is running my code. Or in other words execute after self.output = .calculating but before self.latestKey is re-set to the new object. If it could, then the stale calculation output could be displayed to the user.
I'm wondering if it's possible for the closure that's pushed to DispatchQueue.main to execute while the main thread is running my code
No, it isn't possible. The main queue is a serial queue. If code is running on the main queue, no "other" main queue code can run. Your DispatchQueue.main.async effectively means: "Wait until all code running on the main queue comes naturally to an end, and then run this on the main queue."
On the other hand, DispatchQueue.global() is not a serial queue. Thus it is theoretically possible for two calls to calculateOutput to overlap. That isn't something you want to have happen; you want to be sure that any executing instance of calculateOutput finishes (and we proceed to grapple with the latestKey) before another one can start. In other words, you want to ensure that the sequence
set latestKey on the main thread
perform calculateOutput in the background
look at latestKey on the main thread
happens coherently. The way to ensure that is to set aside a DispatchQueue that you create with DispatchQueue(label:), that you will always use for running calculateOutput. That queue will be a serial queue by default.
So here we go: given a Confluent.Kafka IConsumer<>, it wraps it into a dedicated async CE and consumes as long as cancellation hasn't been requested. This piece of code is also defends itself against the OperationCancelledException and runs finally block to ensure graceful termination of consumer.
let private consumeUntiCancelled callback (consumer: IConsumer<'key, 'value>) =
async {
let! ct = Async.CancellationToken
try
try
while not ct.IsCancellationRequested do
let consumeResult = consumer.Consume(ct)
if not consumeResult.IsPartitionEOF then do! (callback consumeResult)
with
| :? OperationCanceledException -> return ()
finally
consumer.Close()
consumer.Dispose()
}
Question #1: is this code correct or am I abusing the async?
So far so good. In my app I have to deal with lots of consumers that must die altogether. So, assuming that consumers: seq<Async<unit>> represents them, the following code is what I came up with:
async {
for consumer in consumers do
do! (Async.StartChild consumer |> Async.Ignore).
}
I expect this code to chain childs to the parent's cancellation context, and once it is cancelled, childs gonna be cancelled as well.
Question #2: is my finally block guaranteed to be ran even though child got cancelled?
I have two observations about your code:
Your use of Async.StartChild is correct - all child computations will inherit the same cancellation token and they will all get cancelled when the main token is cancelled.
The async workflow can be cancelled after you call consumer.Consume(ct) and before you call callback. I'm not sure what this means for your specific problem, but if it removes some data from a queue, the data could be lost before it is processed. If that's an issue, then I think you'll need to make callback non-asynchronous, or invoke it differently.
In your consumeUntilCancelled function, you do not explicity need to check while not if ct.IsCancellationRequested is true. The async workflow does this automatically in every do! or let!, so you can replace this with just a while loop.
Here is a minimal stand-alone demo:
let consume s = async {
try
while true do
do! Async.Sleep 1000
printfn "%s did work" s
finally
printfn "%s finalized" s }
let work =
async {
for c in ["A"; "B"; "C"; "D"] do
do! Async.StartChild (consume c) |> Async.Ignore }
Now we create the computation with a cancellation token:
// Run this in F# interactive
let ct = new System.Threading.CancellationTokenSource()
Async.Start(work, ct.Token)
// Run this sometime later
ct.Cancel()
Once you call ct.Cancel, all the finally blocks will be called and all the loops will stop.
I have two Observables and I want them both to terminate with completion event when either of them is completed. They both branch from the same sequence, but have different termination condition:
.filter.take(1)
.distinctUntilChanged.take(2)
How can I have two Observables complete together when either one completes?
I think you need to manage subscriptions manually, here is an example:
var disposable1: Disposable?
var disposable2: Disposable?
disposable1 = observable.filter().take(1).subscribe(onDisposed: {
disposable2?.dispose()
})
disposable2 = observable.distinctUntilChanged().take(2).subscribe(onDisposed: {
disposable1?.dispose()
})
I am trying to create a stream that polls a network service. At the moment it queries the service then completes after a short delay. I'd like the onward stream to restart rather than completing thereby polling the service forever.
You could do something like ...
myPollingStream.repeat()
But repeat in RxSwift is actually repeatElement and so actually generates a stream of observables. You could possibly concatMap these into a flattened serial sequence but RxSwift does not have the concatMap operator.
So how do I loop an observable in RxSwift?
I'd like the requests to be sequential, not concurrent so flatMap is not an option since it merges streams leading to overlapping requests. I'm looking for something similar to how retry() works but restarting onComplete not onError
Observable.repeatElement(myPollingStream, scheduler: MainScheduler.instance).concat()
repeatElement(_:scheduler:) will create an infinite stream of polling queries.
contat() will then make sure each polling query is completed before subscribing to the next.
Attention
While the above works in theory, without a backpressure implemetation, repeatElements(_:scheduler:) will emit events until you eventually run out of memory. This makes this solution not viable as of RxSwift 3.0. More details can be found in this issue on RxSwift repository.
Option 1: Recursive function
Your myPollingStream:
func myPollingStream() -> Observable<Result> {
return Observable<String>.create { observer in
// your network code here
return Disposables.create()
}
}
Then you create a a recursive function:
func callMyPollingStream() {
myPollingStream()
.subscribe(onNext: { result in
callMyPollingStream() // when onNext or onCompleted, call it again
})
.addDisposableTo(db)
}
Option 2: Use interval
let _ = Observable<Int>
.interval(5, scheduler: MainScheduler.instance)
.subscribe(onNext: { _ in
let _ = myPollingStream().subscribe()
})
.addDisposableTo(db)
With this option, myPollingStream() function will be called every 5 seconds.