Async Void, ASP.Net, and Count of Outstanding Operations
Microsoft made the decision to avoid as much backwards-compatibility issues as possible when bringing async
into ASP.NET. And they wanted to bring it to all of their "one ASP.NET" - so async
support for WinForms, MVC, WebAPI, SignalR, etc.
Historically, ASP.NET has supported clean asynchronous operations since .NET 2.0 via the Event-based Asynchronous Pattern (EAP), in which asynchronous components notify the SynchronizationContext
of their starting and completing. .NET 4.5 brings the first fairly hefty changes to this support, updating the core ASP.NET asynchronous types to better enable the Task-based Asynchronous Pattern (TAP, i.e., async
).
In the meantime, each different framework (WebForms, MVC, etc) all developed their own way to interact with that core, keeping backwards compatibility a priority. In an attempt to assist developers, the core ASP.NET SynchronizationContext
was enhanced with the exception you're seeing; it will catch many usage mistakes.
In the WebForms world, they have RegisterAsyncTask
but a lot of people just use async void
event handlers instead. So the ASP.NET SynchronizationContext
will allow async void
at appropriate times during the page lifecycle, and if you use it at an inappropriate time it will raise that exception.
In the MVC/WebAPI/SignalR world, the frameworks are more structured as services. So they were able to adopt async Task
in a very natural fashion, and the framework only has to deal with the returned Task
- a very clean abstraction. As a side note, you don't need AsyncController
anymore; MVC knows it's asynchronous just because it returns a Task
.
However, if you try to return a Task
and use async void
, that's not supported. And there's little reason to support it; it would be quite complex just to support users that aren't supposed to be doing that anyway. Remember that async void
notifies the core ASP.NET SynchronizationContext
directly, bypassing the MVC framework completely. The MVC framework understands how to wait for your Task
but it doesn't even know about the async void
, so it returns completion to the ASP.NET core which sees that it's not actually complete.
This can cause problems in two scenarios:
- You're trying to use some library or whatnot that uses
async void
. Sorry, but the plain fact is that the library is broken, and will have to be fixed. - You're wrapping an EAP component into a
Task
and properly usingawait
. This can cause problems because the EAP component interacts withSynchronizationContext
directly. In this case, the best solution is to modify the type so it supports TAP naturally or replace it with a TAP type (e.g.,HttpClient
instead ofWebClient
). Failing that, you can use TAP-over-APM instead of TAP-over-EAP. If neither of those are feasible, you can just useTask.Run
around your TAP-over-EAP wrapper.
Regarding "fire and forget":
I personally never use this phrase for async void
methods. For one thing, the error handling semantics most certainly do not fit in with the phrase "fire and forget"; I half-jokingly refer to async void
methods as "fire and crash". A true async
"fire and forget" method would be an async Task
method where you ignore the returned Task
rather than waiting for it.
That said, in ASP.NET you almost never want to return early from requests (which is what "fire and forget" implies). This answer is already too long, but I have a description of the problems on my blog, along with some code to support ASP.NET "fire and forget" if it's truly necessary.
Why does Exception from async void crash the app but from async Task is swallowed
TL;DR
This is because async void
shouldn't be used! async void
is only there to make legacy code work (e.g. event handlers in WindowsForms and WPF).
Technical details
This is because of how the C# compiler generates code for the async
methods.
You should know that behind async
/await
there's a state machine (IAsyncStateMachine
implementation) generated by the compiler.
When you declare an async
method, a state machine struct
will be generated for it. For your ex()
method, this state machine code will look like:
void IAsyncStateMachine.MoveNext()
{
try
{
throw new Exception();
}
catch (Exception exception)
{
this.state = -2;
this.builder.SetException(exception);
}
}
Note that this.builder.SetException(exception);
statement. For a Task
-returning async
method, this will be an AsyncTaskMethodBuilder
object. For a void ex()
method, it will be an AsyncVoidMethodBuilder
.
The ex()
method body will be replaced by the compiler with something like this:
private static Task ex()
{
ExAsyncStateMachine exasm;
exasm.builder = AsyncTaskMethodBuilder.Create();
exasm.state = -1;
exasm.builder.Start<ExAsyncStateMachine>(ref exasm);
return exasm.builder.Task;
}
(and for the async void ex()
, there will be no last return
line)
The method builder's Start<T>
method will call the MoveNext
method of the state machine. The state machine's method catches the exception in its catch
block. This exception should normally be observed on the Task
object - the AsyncTaskMethodBuilder.SetException
method stores that exception object in the Task
instance. When we drop that Task
instance (no await
), we don't see the exception at all, but the exception itself isn't thrown anymore.
In the state machine for async void ex()
, there's an AsyncVoidMethodBuilder
instead. Its SetException
method looks different: since there's no Task
where to store the exception, it has to be thrown. It happens in a different way, however, not just a normal throw
:
AsyncMethodBuilderCore.ThrowAsync(exception, synchronizationContext);
The logic inside that AsyncMethodBuilderCore.ThrowAsync
helper decides:
- If there's a
SynchronizationContext
(e.g. we're on a UI thread of a WPF app), the exception will be posted on that context. - Otherwise, the exception will be queued on a
ThreadPool
thread.
In both cases, the exception won't be caught by a try-catch
block that might be set up around the ex()
call (unless you have a special SynchronizationContext
that can do this, see e.g. Stephen Cleary's AsyncContext
).
The reason is simple: when we post a throw
action or enqueue it, we then simply return from the ex()
method and thus leave the try-catch
block. Then, the posted/enqueued action is executed (either on the same or on a different thread).
Is it recommended/acceptable to run an async void method into a new thread?
But as I said, this action is supposed to last as long as the whole application
No, it doesn't. The method will return almost immediately. That's what it means for a method to be asynchronous. It doesn't block the caller while the work is being done.
Creating a new thread just to call an asynchronous method is like hiring someone to come to your house to put a letter in your mailbox for you. It's more work than just doing it yourself, because the actual act of getting the letter to its destination doesn't actually prevent you from doing work outside of a trivial bit of work to just get it started.
How(and why) can I avoid returning a void on these async methods?
Constructor are meant to bring an object to it's fully constructed structure once initialized. On the other hand, async methods and constructors don't play well together, as a constructor is inherintly synchronous.
The way to get around this problem is usually to expose an initialization method for the type, which is itself async. Now, you let the caller initialize the object fully. Note this will require you to monitor the actual initialization of the method.
Async shines when you need scale. If you don't expect this to be an IO bottleneck in your application, perhaps consider using synchronous methods instead. This will give you the benefit of actually fully initializing your object once the constructor finishes execution. Although, I don't think I would initiate a call to a database via a constructor anyway:
public async Task InitializeAsync()
{
LoadFromLocal();
await AttemptDatabaseLoadAsync();
}
public async Task AttemptDatabaseLoadAsyncAsync()
{
while(ConnectionAttempts < MAX_CONNECTION_ATTEMPTS)
{
Task<bool> Attempt = TryLoad ();
bool success = await Attempt;
if (success)
{
//call func to load data into program memory proper
}
else
{
ConnectionAttempts++;
}
}
}
And call it:
var dataLoader = new DataLoader();
await dataLoader.InitializeAsync();
Can void async method be used in custom ActionResult?
As Stephen said, I can't use async
ability inside ExecuteResult
in MVC 5.0. Since my goal was a little bit refactoring I had to use ContinueWith:
public override void ExecuteResult(ControllerContext context)
{
//....
hubConnection.Start().ContinueWith(task =>
{
if (task.IsCompleted)
{
notification.Invoke(MethodName);
}
});
InnerResult.ExecuteResult(context);
}
Now it works like a charm.
Exception when using await on async function
The culprit here is the fact that you're invoking Post with an async void method:
internal static async void PostTwitter
Async void functions exists solely for event handling, and have very odd and unpredictable error handling when used outside of this context. See this MSDN article for information.
Switch your async void method to an async Task method and some actual errors may start to bubble to the surface:
internal static async Task PostTwitter
Stephen Cleary actually goes into this problem in great detail in another SO post. To quote from him directly regarding the behavior of asynchronous methods:
Historically, ASP.NET has supported clean asynchronous operations since .NET 2.0 via the Event-based Asynchronous Pattern (EAP), in which asynchronous components notify the SynchronizationContext of their starting and completing
Whenever your code calls into SocialTwitter.PostTwitter, the PostTwitter method notifies the SynchronizationContext that it's started, bypassing whichever framework you're using completely and going straight to the ASP.NET core. The PostTwitter method than starts the async Post method, which also notifies the SynchronizationContext that it has started.
The ASP.NET frameworks understand how to wait for Tasks, but, excluding WinForms and WebForms in very specific situations, know little about how to handle async void methods. Thus the framework believes the async void to be complete and it attempts to notify the SychronizationContext that the async void method has finished, even though the async void method spawned an async Task method that's still running. As part of a series of safety nets designed specifically to catch errors like this, the SynchronizationContext throws the InvalidOperationException that you've been seeing.
Implementing a Void interface signature with an Async method
What would be the best way to program against an interface (supposing you don't have control over changing any of the interface signatures) with a void return type when the method needs to be async?
This is essentially the same as asking "what is the best way to block on asynchronous code", and the answer is the same: there is no best way. There are a variety of hacks - as I describe in an MSDN article - but there is no hack that works in every scenario.
The blocking-threadpool-hack (Task.Run(async () => await ExternalProcess()).GetAwaiter().GetResult();
) probably works in the most scenarios; it only fails if ExternalProcess
has to access something tied to a specific context (e.g., UI elements or HttpContext.Current
), or if it depends on the implicit synchronization provided by a one-thread-at-a-time context. So as long as your ExternalProcess
doesn't need a specific context and if it's thread-safe, then the blocking thread pool hack should work.
Or vice-versa, how does one program against a asyc Task return type when the method doesn't have any asyncronous code?
This one's easy. An async-compatible (e.g., Task
-returning) interface method means that the implementation may be asynchronous, not that it must be asynchronous. Synchronous implementations can use Task.FromResult
and Task.FromException
to create the returned Task
. Alternatively, you can mark the method async
and ignore the compiler warning, which is the approach taken by my AsyncEx library.
asynchronous module or handler completed while an asynchronous operation was still pending
Finally, I resolved the above exception when I am return Task instead of void in DeliveryProgressReport method. and also where ever I was called the await DeliveryProgressReport() method there also I return Task instead of void.
-Pradeep
Related Topics
Built in .Net Algorithm to Round Value to the Nearest 10 Interval
Cannot Access a Disposed Object in ASP.NET Core When Injecting Dbcontext
How Can a Windows Service Start a Process When a Timer Event Is Raised
Removing Nodes from an Xmldocument
Jtoken: Get Raw/Original JSON Value
Difference with Parameters.Add and Parameters.Addwithvalue
Default Parameter for Value Must Be a Compile Time Constant
Authorization Header Is Lost on Redirect
How to Ensure a Form Displays on the "Additional" Monitor in a Dual Monitor Scenario
How to Disable a Tab Inside a Tabcontrol
How to Unload an Assembly from the Primary Appdomain
How to Hide a Column (Gridview) But Still Access Its Value
Databindings Don't Seem to Refresh
Retrieve Image from Database in ASP.NET
What Is Quicker, Switch on String or Elseif on Type
What Are the Limitations of SQLdependency
Custom App.Config Section with a Simple List of "Add" Elements