>If there is any other code that touches account.balance from any other thread of execution (OS thread, coroutine, etc), then this code is already not safe.
My point is about concurrency, not parallelism. I didn't mention anything about threads, let alone that there's more than one thread involved or that threads are being spawned.
Take a simple node.js program or a Rust program that has a single-threaded tokio executor, or a C# WinForm program that reuses the UI thread for running Tasks, and everything I wrote applies. A single Account value used to be fine to be shared between coroutines because only one invocation of ChargeAccount would be active at a time, but the "transparent async" breaks that assumption.
Explicit async-await makes it more obvious that multiple invocations of ChargeAccount might interleave unless proven otherwise, and the programmer has a reason to confirm that it is not the case, or use locking / atomics as necessary.
>You seem to assume it is equivalent to something like this, which would indeed be unsafe, but is not the case:
That `Task.WaitAll` program is indeed what I'm talking about, though the part of you thinking that I was talking about it being equivalent to a multi-threaded program is bogus.
> Take a simple node.js program or a Rust program that has a single-threaded tokio executor, or a C# WinForm program that reuses the UI thread for running Tasks, and everything I wrote applies. A single Account value used to be fine to be shared between coroutines because only one invocation of ChargeAccount would be active at a time, but the "transparent async" breaks that assumption.
That's not true at all. Here is a simple NodeJS implementation that is completely unsafe despite having non-transparent async:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function LoggerWrite(s) {
await delay(Math.random() * 100);
console.log(s);
}
async function ChargeAccount(account, cents) {
let balance = account.balance;
let newBalance = balance - cents;
await LoggerWrite("changing account from "+ balance + " to " + newBalance);
account.balance = newBalance;
}
async function Main() {
let account = {balance: 0};
let t1 = ChargeAccount(account, 1);
let t2 = ChargeAccount(account, 20);
let t3 = ChargeAccount(account, 18);
let t4 = ChargeAccount(account, 12);
await Promise.all([t1, t2, t3, t4]);
console.log(account);
}
Main()
//will randomly end up with -1 or -12 or -18 or -20, instead of the expected -51
And here is a Go program that does the right thing even with transparent async:
Edit:
> Explicit async-await makes it more obvious that multiple invocations of ChargeAccount might interleave unless proven otherwise, and the programmer has a reason to confirm that it is not the case, or use locking / atomics as necessary.
As shown by the example above, I think almost the opposite is true: async/await take code that looks linear and single-threaded and make it run concurrently. It's easy to accidentally end up with code like the JS one. Even worse, it's easy to accidentally call async code without await-ing it at all, and have unexpected behavior.
By contrast, transparent async + cheap coroutines means that you can make all of your APIs blocking, and rely on the user explicitly spawning a coroutine if they want any kind of parallel/concurrent behavior, at the call site. In the Go code, I could have called `go ChargeAccount(...)` if I wanted the ChargeAccount calls to happen in parallel - I would have explicitly created parallelism, and I would have known to be careful of sharing the same pointer with the coroutines running concurrently.
>Here is a simple NodeJS implementation that is completely unsafe despite having non-transparent async:
What does that have to do with my comment?
I said (paraphrased) "Transparent async is bad because sync code can end up being wrong when async-ness is silently introduced into it". You keep responding with (paraphrased) "Explicit async code can also be bad." Do you understand how this does nothing to negate what I said?
(This discussion is frustrating, and the walls of irrelevant text you keep posting aren't helping.)
My point is about concurrency, not parallelism. I didn't mention anything about threads, let alone that there's more than one thread involved or that threads are being spawned.
Take a simple node.js program or a Rust program that has a single-threaded tokio executor, or a C# WinForm program that reuses the UI thread for running Tasks, and everything I wrote applies. A single Account value used to be fine to be shared between coroutines because only one invocation of ChargeAccount would be active at a time, but the "transparent async" breaks that assumption.
Explicit async-await makes it more obvious that multiple invocations of ChargeAccount might interleave unless proven otherwise, and the programmer has a reason to confirm that it is not the case, or use locking / atomics as necessary.
>You seem to assume it is equivalent to something like this, which would indeed be unsafe, but is not the case:
That `Task.WaitAll` program is indeed what I'm talking about, though the part of you thinking that I was talking about it being equivalent to a multi-threaded program is bogus.
reply