我最近一直在将一个包含旧版 ASMX Web 服务的 ASP.NET 2.0 项目更新到 ASP.NET 4.5。
首先要做的是确保httpRuntime@targetFramework is set to 4.5 in your web.config。
从未检测到父任务(即返回任务的 ASMX 中的方法调用)完成。
这实际上是一个典型的死锁情况。我describe it in full on my blog,但它的要点是await 将(默认情况下)捕获“上下文”并使用它来恢复async 方法。在这种情况下,该“上下文”是一个 ASP.NET 请求上下文,它一次只允许一个线程。因此,当 asmx 代码在任务上进一步阻塞(通过WaitAll)时,它会阻塞该请求上下文中的线程,并且async 方法无法完成。
将阻塞等待推送到后台线程会“起作用”,但正如您所注意到的,这有点暴力。一个小的改进是只使用var result = Task.Run(() => MethodAsync()).Result;,它将后台工作排队到线程池,然后阻塞请求线程等待它完成。或者,您可以选择为每个await 使用ConfigureAwait(false),这会覆盖默认的“上下文”行为并允许async 方法在请求外部的线程池线程上继续上下文。
但更好的改进是“一直”使用异步调用。 (旁注:我在MSDN article on async best practices 中对此进行了更详细的描述)。
ASMX does allow APM variety 的异步实现。我建议您首先使您的 asmx 实现代码尽可能异步(即,使用 await WhenAll 而不是 WaitAll)。你最终会得到一个“核心”方法,然后你需要wrap in an APM API。
包装器看起来像这样:
// 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();
}
如果你有很多方法要包装,这会有点乏味,所以我写了一些ToBegin and ToEnd methods 作为我的AsyncEx library 的一部分。使用这些方法(或者如果您不想要库依赖,您可以自己复制它们),包装器可以很好地简化:
[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);
}