You have it backward. The compiler should implicitly add the awaits for waitable objects, unless an operation is explicitly async.
So you would write (in pseudocode):
Task PlaceOrder(Order order) { SaveOrder(order); DeductItems(order); SendConfirmationEmail(order); }
And the compiler will implicitly await all three operations (and ideally infer that your function is async).
If you want to overlap computation, you avoid the implicit wait with async:
Task PlaceOrders(Order order1, Order order2) {
let done1 = async PlaceOrder(order1); // async prevents implicit waiting
let done2 = async PlaceOrder(order2);
wait_all(done1, done2); // wait_all is also async and implicitly awaited. Ideally this should happen automatically for all unwaited and not returned futures at end of scope
}
This allows being polymorphic on the async-ness of the function (pardon the pseudo c++):
template F >
void for_each(R range, F f) {
for (auto x : range) f(x); // f(x) is awaited if f is async and for_each itself becomes async.
}
edit: sometimes it is important that no preemption happens in a region [1], so some scoped marker (atomic { ... } for example) would case a compilation error if an await would be introduced automatically.
edit2: and of course you should be able to use async even if the called function is a boring old blocking one. The runtime can spawn background task (or better yet use work-stealing) to run it.
[1] personally I think that atomicity guarantees should be about data, not code, but whatever.