Async Void, ASP.NET, and Count of Outstanding Operations

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:

  1. 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.
  2. You're wrapping an EAP component into a Task and properly using await. This can cause problems because the EAP component interacts with SynchronizationContext 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 of WebClient). Failing that, you can use TAP-over-APM instead of TAP-over-EAP. If neither of those are feasible, you can just use Task.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



Leave a reply



Submit