c# - 从 ASMX 调用基于任务的方法

标签 c# asp.net-web-api task-parallel-library asmx

我想分享一个最近的经验,它可能对任何必须维护必须更新以调用基于任务的方法的遗留 ASMX Web 服务的人有所帮助。

我最近一直在将一个 ASP.NET 2.0 项目更新到 ASP.NET 4.5,该项目包括一个遗留的 ASMX Web 服务。作为更新的一部分,我引入了一个 Web API 接口(interface)以实现应用程序的高级自动化。 ASMX 服务必须与新的 API 共存以实现向后兼容性。

该应用程序的功能之一是能够代表调用者从外部数据源(工业工厂历史学家、定制网络服务等)请求数据。作为升级的一部分,我重新编写了数据访问层的重要部分,以使用基于任务的异步模式异步请求数据。鉴于无法在 ASMX 服务中使用 aync/await,我修改了 ASMX 方法以阻塞调用异步方法,即调用基于任务的方法,然后使用 Task.WaitAll 阻塞线程直到任务完成。

当调用任何在后台调用返回 Task 或 Task 的方法的 ASMX 方法时,我发现请求总是超时。当我单步执行代码时,我可以看到异步代码已成功执行,但对 Task.WaitAll 的调用从未检测到任务已完成。

这引起了一个大问题:ASMX 服务如何与新的异步数据访问功能愉快地共存?

最佳答案

I've recently been updating an ASP.NET 2.0 project that includes a legacy ASMX web service to ASP.NET 4.5.

首先要做的是确保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.

这实际上是一个典型的死锁情况。我 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);
}

关于c# - 从 ASMX 调用基于任务的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24078621/

相关文章:

c# - 有没有一种惯用的方法来路由在 TPL 数据流图中的 TransformBlock 中失败的元素?

c# - Autofac - 如何创建带参数的生成工厂

c# - 管理源代码的方法

C# 在窗体面板中打开其他程序

c# - 为什么 Interlocked.Increment 在 Parallel.ForEach 循环中给出不正确的结果?

c# - 在 TPL 上执行长时间任务时图像渲染速度缓慢

c# - 无法终止进程 : "No process is associated with this object" error

authentication - 通过代码使用 SSO 在多台机器上授权 WebAPI

c# - 使用 OWIN 从解决方案中的 WPF 项目启动 asp.net WebApi 项目

c# - .net webapi 文件路径作为参数