We want to invoke a long-running activity asynchronously and after sometime based on the external signal, would like to cancel that long-running activity.
Async.procedure(activities::longRunningActivity)
// Execute some synchronous activities
Workflow.await(() -> !messageQueue.isEmpty());
if (messageQueue.remove(0) == "something") {
// Cancel longRunningActivity
}
Currently the only way for an activity to learn about cancellation is through heartbeating. Make sure that your activity heartbeats and doesn't swallow the exception the heartbeat method throws.
Use CancellationScope:
CancellationScope longRunningCancellationScope =
Workflow.newCancellationScope(
() -> Async.procedure(activities::longRunningActivity));
longRunningCancellationScope.run();
// Execute some synchronous activities
Workflow.await(() -> !messageQueue.isEmpty());
if (messageQueue.remove(0) == "something") {
longRunningCancellationScope.cancel();
}
Related
When we connect to a remote host via tcp, it can be a time-consuming operation. And while waiting for a connection, the user may cancel the operation at any time.
When connecting using async tokio, TcpStream::connect() returns a Future<TcpStream, io::Error> object, assumed to be called tcps_ft.
There are two parts, one is the normal logic of the program, which should call .awati() on tcp_ft above, and the other part is the UI of the program, where the user wants to call drop(tcps_ft) if he clicks the cancel button. But this seems impossible to do, because both calls consume tcps_ft.
#[tokio::test]
async fn test_cancel_by_drop() {
let addr = "192.168.1.100:8080";
let tcps_ft = TcpStream::connect(addr);
let mut tcps = tcps_ft.await.unwrap();
// simulate user's operation.
let cancel_jh = tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(100)).await;
drop(tcps_ft); // can not compile:: tcps_ft moved when await
});
// simulate user's program
tcps.shutdown().await;
cancel_jh.await;
}
So I considered using Task to do it, after all the Task::abort() function will not consume the atjh: Future<JoinHandle> object corresponding to this task. But I still can't call atjh.await before abort() returns synchronously, and in any case, await will consume the variable, making it impossible to call abort() asynchronously. (In other words, the call to abort() must be executed synchronously before await.)
#[tokio::test]
async fn test_cancel_by_abort() {
let addr = "192.168.1.100:8080";
let atjh = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
// simulate user's operation.
let cancel_jh = tokio::spawn(async {
tokio::time::sleep(Duration::from_millis(100)).await;
&atjh.abort();
});
// simulate user's program
let mut tcps = atjh.await.unwrap(); // can not compile:: atjh moved when await
tcps.shutdown().await;
cancel_jh.await;
}
Of course, one less direct way is to use callback functions. In my asynchronous connection task, when connect().await returns, the user's callback function is called to notify the user to call atjh.await.
But here the callback function is introduced again, and I know await/async itself is designed to solve the callback hell problem.
Further, for user-supplied asynchronous callback functions, the compiler may impose very many requirements, such as implementing Send, avoiding cross-thread safety issues, etc. This is certainly not something that async would like to encounter.
How can I do it asynchronously and gracefully to cancel this asynchronous connection process? Is there a suggested model to handle it?
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.
In ReactiveX an Observable might invoke
the following methods
onNext
onError
onCompleted
according to a very clear contract http://reactivex.io/documentation/contract.html
I am writing the code for the Observable and I have realized that under certain circumstances I might invoke onNext within the same thread of execution.
Is that a mistake ?
For example, if I have code like this:
// call onNext twice on the same thread execution.
o.onNext(event1);
o.onNext(event2);
Should I rewrite it like this:
// call onNext and then schedule the next call via setTimeout
o.onNext(event1);
setTimeout(function() { o.onNext(event2); },0);
= = = = = = = =
Clarification: why am I asking ?
In a browser, let's imagine the observer wants to update an HTML element with the content of onNext then if it is called serially that code would not work.
function onNext() {
count++;
htmlElement.innerHTML = 'count:'+count; // <-- this will work only if onNext is invoked on different thread of executions.
}