I've recently been updating an ASP.NET 2.0 project that includes a legacy ASMX web service to ASP.NET 4.5.
The first thing to do is ensure that httpRuntime@targetFramework
is set to 4.5
in your web.config
.
the parent task (i.e. the method call in the ASMX that returned a Task) was never detected as completing.
This is actually a classic deadlock situation. I describe it in full on my blog, but the gist of it is that await
will (by default) capture a "context" and use that to resume the async
method. In this case, that "context" is an ASP.NET request context, which only allows one thread at a time. So, when the asmx code further up the stack blocks on the task (via WaitAll
), it is blocking a thread in that request context, and the async
method cannot complete.
Pushing the blocking wait to a background thread would "work", but as you note it is a bit brute-force. A minor improvement would be to just use var result = Task.Run(() => MethodAsync()).Result;
, which queues the background work to the thread pool and then blocks the request thread waiting for it to complete. Alternatively, you may have the option of using ConfigureAwait(false)
for every await
, which overrides the default "context" behavior and allows the async
method to continue on a thread pool thread outside the request context.
But a much better improvement would be to use asynchronous calls "all the way". (Side note: I describe this in more detail in an MSDN article on async
best practices).
ASMX does allow asynchronous implementations of the APM variety. I recommend that you first make your asmx implementation code as asynchronous as possible (i.e., using await WhenAll
rather than WaitAll
). You'll end up with a "core" method that you then need to wrap in an APM API.
The wrapper would look something like this:
// Core async method containing all logic.
private Task<string> FooAsync(int arg);
// Original (synchronous) method looked like this:
// [WebMethod]
// public string Foo(int arg);
[WebMethod]
public IAsyncResult BeginFoo(int arg, AsyncCallback callback, object state)
{
var tcs = new TaskCompletionSource<string>(state);
var task = FooAsync(arg);
task.ContinueWith(t =>
{
if (t.IsFaulted)
tcs.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCanceled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(t.Result);
if (callback != null)
callback(tcs.Task);
});
return tcs.Task;
}
[WebMethod]
public string EndFoo(IAsyncResult result)
{
return ((Task<string>)result).GetAwaiter().GetResult();
}
This gets a bit tedious if you have a lot of methods to wrap, so I wrote some ToBegin
and ToEnd
methods as part of my AsyncEx library. Using these methods (or your own copy of them if you don't want the library dependency), the wrappers simplify nicely:
[WebMethod]
public IAsyncResult BeginFoo(int arg, AsyncCallback callback, object state)
{
return AsyncFactory<string>.ToBegin(FooAsync(arg), callback, state);
}
[WebMethod]
public string EndFoo(IAsyncResult result)
{
return AsyncFactory<string>.ToEnd(result);
}