Async/Await - Is It *Concurrent*

Async/Await - is it *concurrent*?

It is concurrent, in the sense that many outstanding asychronous operations may be in progress at any time. It may or may not be multithreaded.

By default, await will schedule the continuation back to the "current execution context". The "current execution context" is defined as SynchronizationContext.Current if it is non-null, or TaskScheduler.Current if there's no SynchronizationContext.

You can override this default behavior by calling ConfigureAwait and passing false for the continueOnCapturedContext parameter. In that case, the continuation will not be scheduled back to that execution context. This usually means it will be run on a threadpool thread.

Unless you're writing library code, the default behavior is exactly what's desired. WinForms, WPF, and Silverlight (i.e., all the UI frameworks) supply a SynchronizationContext, so the continuation executes on the UI thread (and can safely access UI objects). ASP.NET also supplies a SynchronizationContext that ensures the continuation executes in the correct request context.

Other threads (including threadpool threads, Thread, and BackgroundWorker) do not supply a SynchronizationContext. So Console apps and Win32 services by default do not have a SynchronizationContext at all. In this situation, continuations execute on threadpool threads. This is why Console app demos using await/async include a call to Console.ReadLine/ReadKey or do a blocking Wait on a Task.

If you find yourself needing a SynchronizationContext, you can use AsyncContext from my Nito.AsyncEx library; it basically just provides an async-compatible "main loop" with a SynchronizationContext. I find it useful for Console apps and unit tests (VS2012 now has built-in support for async Task unit tests).

For more information about SynchronizationContext, see my Feb MSDN article.

At no time is DoEvents or an equivalent called; rather, control flow returns all the way out, and the continuation (the rest of the function) is scheduled to be run later. This is a much cleaner solution because it doesn't cause reentrancy issues like you would have if DoEvents was used.

Are swift actors concurrent, but not parallel?

In answer to your question, independent actor instances can run in parallel (as long as the cooperative thread pool is not constrained). When I ran your code snippets, I enjoyed parallel execution, so I suspect that there is some other aspect of your tests that yielded the serial behavior. We need a MCVE to diagnose this further.

For example, if you run this on an iOS simulator, that has an artificially constrained cooperative thread pool. See Maximum number of threads with async-await task groups. While I have been unable to manifest the behavior you describe, the simulator’s constrained cooperative thread pool can easily lead one to incorrect inferences about the potential parallelism.

If you ran this on a simulator, try testing this on a physical device or a macOS target, and you will enjoy a cooperative thread pool that will avail itself of all of the cores available on your device.

Promises vs async await behaviour javascript. Concurrency mode

To answer your questions, let me first try to explain how your code is working.

Understanding how your code works

Following steps explain the execution of your code:

  1. Script execution start

  2. Call disallowConcurrency function, passing in the initCmp as an argument. cmpConcurrentFunction is assigned the return value of disallowConcurrency function

  3. Call cmpConcurrentFunction for the first time, passing in 'I am called 1 second later' as an argument. During this invocation, inprogressPromise is a resolved promise returned by Promise.resolve(). Awaiting it pauses the function execution.

  4. Call cmpConcurrentFunction for the second time, passing in 'I am called 2 seconds later' as an argument. During this second invocation, inprogressPromise is still a resolved promise returned by Promise.resolve(). Awaiting it pauses the function execution.

  5. Synchronous execution of script ends here. Event loop can now start processing the micro-task queue

  6. Function paused as a result of first invocation of cmpConcurrentFunction is resumed, calling then() method on the promise returned by Promise.resolve(). The value of the inprogressPromise is updated by assigning it a new promise returned by inprogressPromise.then(...)

  7. Function paused as a result of second invocation of cmpConcurrentFunction is resumed. From step 6, we know that inprogressPromise is now a promise returned by inprogressPromise.then(...). So, calling then() on it, we are simply creating a promise chain, adding a new then() method call at the end of the promise chain created in step 6.

    At this point, we have a chain that looks like this:

    inProgressPromise
    .then(() => fn('I am called 1 second later'))
    .then(() => fn('I am called 2 seconds later'))
  8. Callback function of the first then method is called, which in turn calls the fn argument, which is initCmp function. Calling initCmp sets up a timer that resolves the promise initCmp returns after 1 second. When the promise is resolved after 1 second, 'I am called 1 second later' is logged on the console.

  9. When the promise returned by the first call to initComp function in the callback function of first then method, is resolved, it resolves the promise returned by the first then method. This leads to the invocation of the callback function of the second then method. This again calls the initComp function, which then returns a new promise that is resolved after another 1 second.

This explains why you see 'I am called 2 seconds later' logged on the console after 2 seconds.


Now answering your questions:

But i don't understand why this keeps working if i remove the await
inprogressPromise

await inprogressPromise serves no purpose in your code other than pausing the calls to cmpConcurrentFunction function. Both calls await the same promise that is returned by Promise.resolve().

So the await is not necessary? Why?

Because the output you see on the console is not because of await, but because of the promise chain (step 7 above) that is constructed as a result of two invocations of the cmpConcurrentFunction function.

Further more i was expecting this to work :

const disallowConcurrency = (fn) => {   
let inprogressPromise = Promise.resolve();

return async (...args) => {
await inprogressPromise;
inprogressPromise = fn(...args);
};
};

The above code doesn't works as your original code because now the promise chain isn't constructed which is key to the output your original code produces.

With this implementation of disallowConcurrency, your code is executed as explained below:

  1. Call cmpConcurrentFunction function, passing in 'I am called 1 second later' as an argument

  2. Awaiting inprogressPromise pauses the function execution

  3. Call cmpConcurrentFunction for the second time, passing in 'I am called 2 seconds later' as an argument

  4. Awaiting inprogressPromise pauses the function execution. At this point, both invocations of cmpConcurrentFunction are paused and both function invocations are awaiting the same promise that was created as a result of calling Promise.resolve().

    This is the key point here: both invocations of cmpConcurrentFunction function are awaiting the same promise that was created by Promise.resolve(). Why are both invocations awaiting the same promise? Because the cmpConcurrentFunction is invoked for the second time before the first invocation of cmpConcurrentFunction is resumed and calls the initComp function

  5. Resume the first invocation of cmpConcurrentFunction, call the initComp function and assign its return value to inprogressPromise.

    Calling initComp function sets up a timer that resolves the promise initComp function returns after 1 second

  6. Resume the second invocation of cmpConcurrentFunction, call the initComp function and assign its return value to inprogressPromise, overwriting the current value of inprogressPromise which is the promise returned by the first call to initComp function.

    Calling initComp function sets up a timer that resolves the promise initComp function returns after 1 second

At this point, initComp function has been invoked twice, setting up two separate timers that resolve their respective promise after 1 second each.

To summarize the differences between the original implementation of disallowConcurrency function and this one is that in the original implementation, you have a promise chain which resolves the promises sequentially whereas in the second implementation, you have two separate promises (that don't depend on each other) and they are resolved after 1 second each.

Call async/await functions in parallel

You can await on Promise.all():

await Promise.all([someCall(), anotherCall()]);

To store the results:

let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Note that Promise.all fails fast, which means that as soon as one of the promises supplied to it rejects, then the entire thing rejects.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.all([happy('happy', 100), sad('sad', 50)])
.then(console.log).catch(console.log) // 'sad'

How to constrain concurrency (like `maxConcurrentOperationCount`) with Swift Concurrency?

One can insert a group.next() call inside the loop after reaching a certain count, e.g.:

func download() async throws {
try await withThrowingTaskGroup(of: Void.self) { group in
for i in 0..<20 {
let source = sourceUrl(for: i)
let destination = destinationUrl(for: i)

if i >= 6 { // max of six at a time
try await group.next()
}

group.addTask {
let (url, _) = try await self.session.download(from: source)
try? FileManager.default.removeItem(at: destination)
try FileManager.default.moveItem(at: url, to: destination)
}
}

try await group.waitForAll()
}
}

That results in no more than six at a time:

Sample Image

Concurrent start with async/await

The issues is that your Promise callbacks are invoked immediately after you declare them.

const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('what'), 10000);
});

const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('?'), 15000);
});

At this point, both callbacks have been invoked. 10000ms from this point, promise1 will resolve. 15000ms from this point, promise2 will resolve.

async function asyncTest() {
console.time();
const p1 = await promise1;

if (p1 === 'what') {
var p2 = await promise2;
}

console.log(p1, p2);
console.timeEnd();
}

asyncTest()

At this point, as both callbacks have already been invoked, both will be resolved within 15000ms, which is what you observed.

In order to stretch this to the 25000ms you were expecting, try rewriting the Promises as follows:

const promise1 = () => new Promise((resolve, reject) => {
setTimeout(() => resolve('what'), 10000);
});

const promise2 = () => new Promise((resolve, reject) => {
setTimeout(() => resolve('?'), 15000);
});

Note these are now function expressions that will not be invoked immediately. Then rewrite your asyncTest function to invoke these expressions instead of simply await the already executing Promises:

async function asyncTest() {
console.time();
const p1 = await promise1();

if (p1 === 'what') {
var p2 = await promise2();
}

console.log(p1, p2);
console.timeEnd();
}

This will force promise2 to wait until promise1 is completed before beginning execution.

Concurrent async/await with sleep

Yes, it still applies. It fundamentally has to be that way because, like the linked answer says, each async function will be running on the same thread - std::thread::sleep knows nothing about async, and so will make the whole thread sleep.

Nodejs (and JavaScript in general) is much more designed around async, so the language primitives and the language runtime are more async-aware in that way.

Concurrent execution of async methods

How would I go about letting them execute in parallel and join the results as they complete?

The simplest approach is just to create all the tasks and then await them:

var task1 = myService.GetData(source1);
var task2 = myService.GetData(source2);
var task3 = myService.GetData(source3);

// Now everything's started, we can await them
var result1 = await task1;
var result1 = await task2;
var result1 = await task3;

You might also consider Task.WhenAll. You need to consider the possibility that more than one task will fail... with the above code you wouldn't observe the failure of task3 for example, if task2 fails - because your async method will propagate the exception from task2 before you await task3.

I'm not suggesting a particular strategy here, because it will depend on your exact scenario. You may only care about success/failure and logging one cause of failure, in which case the above code is fine. Otherwise, you could potentially attach continuations to the original tasks to log all exceptions, for example.



Related Topics



Leave a reply



Submit