Currently, to use asynchronous features, you need to change the method signature to return a Task, but in some cases it would not be possible to do so, e.g. implementing an interface, writing event handlers or the entry point. In these cases developers often end up with highly discouraged async void, ContinueWith calls, or manually created bridges which (to quote from #7476) "is a waste of time and can be a cause of bugs."
It is proposed (#7476) to make this a special case for entry points but it's not the only place that prevents you to use async/await and still there are chances that you will need to write these bridges or consider any other workarounds.
It would be nice to be able to write an async block, which enables us to use await without changing the method signature and still don't lose readability of the code.
void IActionInvoker.Execute(MethodInfo method) {
var task = method.Invoke(null) as Task;
if (task != null) {
ShowIndicator();
task.ContinueWith(t => {
HideIndicator();
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
task.ContinueWith(t => {
HideIndicator();
ShowError(t.Exception);
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
}
}
void IActionInvoker.Execute(MethodInfo method) {
var task = method.Invoke(null) as Task;
if (task != null) {
ShowIndicator();
async {
try { await task; }
catch(Exception ex) { ShowError(ex); }
finally { HideIndicator(); }
}
}
}
This is not the exact translation but something similar should work for the Main method or in other contexts.
@alrz You can mark void methods as async. The reasons why it is considered bad practice would apply to these async blocks as well, you have no mechanism to propagate the result back to the caller since the "async" portion may continue after the method has returned.
I think that #7169 would be more apt to solve issues like these, however since you'd still need a task-like analog you still can't really make a synchronous method signatures asynchronous (while expecting to return the result to the caller.)
Also, wouldn't local functions be sufficient in declaring nested "blocks" of asynchronous code that could could invoke from within a non-async method?
void IActionInvoker.Execute(MethodInfo method) {
var task = method.Invoke(null) as Task;
UpdateIndicator();
async void UpdateIndicator() {
ShowIndicator();
try { await task; }
catch(Exception ex) { ShowError(ex); }
finally { HideIndicator(); }
}
}
You can mark void methods as async. The reasons why it is considered bad practice would apply to these async blocks as well, you have no mechanism to propagate the result back to the caller since the "async" portion may continue after the method has returned.
You can use return anywhere in an async block, it'll get blocked until await returns and then it gets back to the caller. If you're doing this in a synchronous method, this is probably what you want. And if that was not the case you will need ContinueWith calls which I think await is meant to avoid. async blocks just help to reduce noises around Task API in any of these cases. To not lose exceptions, I can think of this to be added to try so when you use async try you will have to catch exceptions anyway.
wouldn't local functions be sufficient in declaring nested "blocks" of asynchronous code that could could invoke from within a non-async method?
This would apply to Main methods as well, but it loses the point of being direct when you're writing these kind of code. But yes, it would be more like an immediately invoked anonymous async local function but it wouldn't be the same. I think it'd be unfortunate to go to all the trouble _just_ for the sake of Main method.
@alrz Making entry points asynchronous is a special case because it is practically the only case where blocking on an asynchronous call is considered safe. Elsewhere you are inviting deadlocks since you cannot know if asynchronous methods called depends on a synchronization context. Having language support for this would encourage a very bad practice.
@alrz You describe the syntax you want to write, but not its semantics. What would it do?
@gafter The idea is that it would get blocked if you return from an async block i.e. turning every await to GetAwaiter().GetResult(), and otherwise it should fire-and-forget the async part. In the latter case the compiler could produce a warning if exceptions are not caught.
@gafter The idea is that it would get blocked if you
returnfrom an async block i.e. turning everyawaittoGetAwaiter().GetResult()
In other words, an async block would not be asynchronous at all, but blocking. Doesn't sound very async to me.
@gafter Same would apply to async Main, the point is to be able to use await instead of falling back to Task API in a syncrounous method. Yes it just feels like async but when you are _returning_ from an asyncrounous part of a syncrounous method, what do you expect? On the other hand, if you did not use return in an async block it executes asynchronously (fire-and-forget) and if you catch exceptions appropriately, there's nothing left to worry about.
The difference of this and async computations in F# is that they are expressions and they have a type. Still, you cannot interact with the resultant value without leaving the monad, i.e. waiting for it to complete, alternatively you can Start it and pass it to ignore which is what we're doing here.
@alrz
On the other hand, if you did not use
returnin an async block it executes asynchronously (fire-and-forget)
That sounds very unintuitive to me. In a void method, adding return; at the end does nothing. But in an async block, it would completely change what it does?
@svick Not always, it might change the type of the enclosing lambda (#7681) :smile:
@alrz
Both of the described behaviors are very much considered bad practices in asynchronous code. The entry point is the _one_ exception. As such I don't think that a general purpose mechanism should be introduced into the language as it would encourage people to use it outside of Main where they absolutely should not be using it.
Fair enough.
Most helpful comment
@alrz
Both of the described behaviors are very much considered bad practices in asynchronous code. The entry point is the _one_ exception. As such I don't think that a general purpose mechanism should be introduced into the language as it would encourage people to use it outside of
Mainwhere they absolutely should not be using it.