Lately I can't seem to get enough of the amazing async-await pattern of C# 5.0. Where have you been all my life?
I'm absolutely thrilled with the simple syntax, but I'm having one small difficulty. My problem is that async functions have a totally different declaration from regular functions. Since only async functions can await on other async functions, when I'm trying to port some old blocking code to async, I'm having a domino effect of functions I have to convert.
People have been referring to this as a zombie infestation. When async gets a bite in your code, it will keep getting bigger and bigger. The porting process is not difficult, it's just throwing async
in the declaration and wrapping the return value with Task<>
. But it is annoying to do this over and over again when porting old synchronous code.
It seems to me it will be much more natural if both function types (async and plain old sync) had the exact same syntax. If this were the case, porting will take zero effort and I could switch painlessly between the two forms.
I think this could work if we follow these rules:
Async functions won't require the
async
declaration anymore. Their return types wouldn't have to be wrapped inTask<>
. The compiler will identify an async function during compilation by itself and do the Task<> wrapping automatically as needed.No more fire-and-forget calls to async functions. If you want to call an async function, you will need to await on it. I hardly use fire-and-forget anyways, and all the examples of crazy race conditions or deadlocks always seem to be based on them. I think they are too confusing and "out of touch" with the synchronous mindset we try to leverage.
If you really can't live without fire-and-forget, there will be special syntax for it. In any case, it won't be part of the simple unified syntax I'm talking about.
The only keyword you need to denote an asynchronous call is
await
. If you have await, the call is asynchronous. If you don't, the call is plain old synchronous (remember, we don't have fire-and-forget anymore).The compiler will identify async functions automatically (since they don't have a special declaration anymore). Rule 4 makes this very simple to do - if a function has an
await
call inside, it is async.
Could this work? or am I missing something? This unified syntax is much more fluid and could solve the zombie infestation altogether.
Some Examples:
// assume this is an async function (has await calls inside)
int CalcRemoteBalanceAsync() { ... }
// assume this is a regular sync function (has no await calls inside)
int CalcRemoteBalance() { ... }
// now let's try all combinations and see what they do:
// this is the common synchronous case - it will block
int b1 = CalcRemoteBalance();
// this is the common asynchronous case - it will not block
int b2 = await CalcRemoteBalanceAsync();
// choosing to call an asynchronous function in a synchronous manner - it will block
// this syntax was used previously for async fire-and-forget, but now it's just synchronous
int b3 = CalcRemoteBalanceAsync();
// strange combination - it will block since it's calling a synchronous function
// it should probably show a compilation warning though
int b4 = await CalcRemoteBalance();
Note: this is a continuation of an interesting related discussion in SO
await
immediately. You can do something likevar task = FooAsync(); Bar(); await task;
. How would I do this in your proposal?async
. I think that's one of the big advantages ofasync
-await
: that it allows you to easily compose asynchronous operations (and not just in the simplest "start A, wait for A, start B, wait for B" way). And there already is special syntax exactly for this purpose: it's calledawait
.