Web API同步调用最佳实践

时间:2015-06-17 15:12:17

标签: c# iis .net-4.5 asp.net-web-api

可能这个问题已经提出,但我从来没有找到明确的答案。假设我在 IIS 上托管了 Web API 2.0 应用程序。我想我明白,从GUI事件到HttpClient调用,始终使用异步方法是最佳实践(以防止客户端出现死锁)。这很好,而且很有效。但是,如果我的客户端应用程序没有GUI(例如,窗口服务,控制台应用程序),而只有同步方法来进行调用,那么最佳做法是什么?在这种情况下,我使用以下逻辑:

void MySyncMethodOnMyWindowServiceApp()
{
  list = GetDataAsync().Result().ToObject<List<MyClass>>();
} 

async Task<Jarray> GetDataAsync()
{
  list = await Client.GetAsync(<...>).ConfigureAwait(false);
  return await response.Content.ReadAsAsync<JArray>().ConfigureAwait(false);
}

但不幸的是,在随机机器上随机出现的情况下,仍会导致客户端出现死锁

客户端应用程序此时停止并且永不返回:

list = await Client.GetAsync(<...>).ConfigureAwait(false);

3 个答案:

答案 0 :(得分:2)

如果它可以在后台运行并且不被强制同步,请尝试在Task.Run()中包装代码(调用异步方法)。我不确定是否会解决“死锁”问题。问题(如果它不同步,那是另一个问题),但是如果你想从async / await中受益,如果你不是一直没有异步,我会#39 ;除非你在后台线程中运行,否则我不确定是否会带来好处。我有一个案例,在几个地方添加Task.Run()(在我的情况下,从我改为异步的MVC控制器)和调用异步方法不仅略微提高了性能,但它提高了可靠性(不确定它在负载较重的情况下,这是一个“死锁”#34;但似乎是类似的东西。

你会发现使用Task.Run()被一些人视为一种不好的方式,但我真的无法在我的情况下看到更好的方法,而且它确实似乎是一种进步。也许这是其中一个理想的方法,而不是让你在不完美的情况下工作的方式。: - )

[由于代码请求而更新]

所以,正如其他人发布的那样,你应该一直向下&#34; async&#34;。在我的情况下,我的数据不是异步,但我的用户界面是。所以,我尽可能地向下异步,然后我用Task.Run包装我的数据调用,这样才有意义。我认为,这就是弄清楚事情是否可以并行运行的诀窍,否则,你只是同步(如果你使用异步并立即解决它,迫使它等待答案)。我有很多可以并行执行的读取。

在上面的例子中,我认为你必须尽可能地同步,然后在某些时候,确定你可以脱离线程并执行独立于其他代码的操作。我们假设你有一个保存数据的操作,但你真的不需要等待回复 - 你要保存它并且你已经完成了。您可能需要注意的唯一事情是不要等待该线程/任务完成而关闭程序。在你的代码中有意义的地方取决于你。

语法非常简单。我接受了现有代码,将控制器更改为异步,返回以前返回的类的任务。

var myTask = Task.Run(() =>
{
   //...some code that can run independently....  In my case, loading data
});
// ...other code that can run at the same time as the above....

await Task.WhenAll(myTask, otherTask); 
//..or...
await myTask;

//At this point, the result is available from the task
myDataValue = myTask.Result;

有关更好的示例,请参阅MSDN:  https://msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx

[更新2,与原始问题更相关]

让我们说您的数据读取是异步方法。

private async Task<MyClass> Read()

您可以调用它,保存任务,并在准备就绪时等待它:

var runTask = Read();
//... do other code that can run in parallel

await runTask;

因此,为此目的,调用异步代码,这是原始海报所要求的,我不认为你需要Task.Run(),虽然我不认为你可以使用&# 34;等待&#34;除非您是异步方法,否则您需要使用替代语法进行等待。

诀窍在于没有一些代码可以并行运行,其中没有什么意义,所以考虑多线程仍然是重点。

答案 1 :(得分:1)

使用Task<T>.Result相当于Wait,它将在线程上执行同步块。在WebApi上使用异步方法,然后让所有调用者同步有效地阻止它们使WebApi方法同步。在加载时,如果同时Waits的数量超过服务器/ app线程池,则会死锁。

所以请记住经验法则&#34; async一直向下&#34;。您希望长时间运行的任务(获取List的集合)是异步的。如果调用方法必须是同步的,那么您希望将该转换从异步转换为同步(使用结果或等待),尽可能靠近&#34;地面&#34;尽可能。让他们长时间运行进程异步并使同步部分尽可能短。这将大大减少线程被阻塞的时间长度。

所以例如你可以做这样的事情。

void MySyncMethodOnMyWindowServiceApp()
{
   List<MyClass> myClasses = GetMyClassCollectionAsync().Result;
}

Task<List<MyClass>> GetMyListCollectionAsync()
{
   var data = await GetDataAsync(); // <- long running call to remote WebApi?
   return data.ToObject<List<MyClass>>();
}

关键部分是长时间运行的任务保持异步并且未被阻止,因为使用了await。

也不要将响应能力与可扩展性混淆。两者都是异步的正当理由。是响应是使用异步的原因(以避免在UI线程上阻塞)。你是对的,这不适用于后端服务,但这并不是为什么在WebApi上使用异步的原因。 WebApi也是一个非GUI后端进程。如果异步代码的唯一优势是UI层的响应性,那么WebApi将是从头到尾的同步代码。使用异步的另一个原因是可伸缩性(避免死锁),这就是为什么WebApi调用是异步的。保持长时间运行的进程异步有助于IIS更有效地使用有限数量的线程。默认情况下,每个核心只有12个工作线程。这可以提出,但这并不是一个神奇的子弹,因为线程相对昂贵(每个线程大约1MB开销)。 await允许您用更少的资源做更多事情。在发生死锁之前,在较少的线程上执行更多并发长时间运行的进程。

答案 2 :(得分:0)

你遇到的死锁问题必须源于其他问题。您使用ConfigureAwait(false)可以防止死锁。解决这个错误,你很好。

请参阅答案为“否”的Should we switch to use async I/O by default?。您应根据具体情况决定,并在收益超过成本时选择异步。重要的是要了解异步IO具有与之相关的生产力成本。在非GUI场景中,只有少数目标场景从异步IO中获得任何好处。但是,好处可能是巨大的,但仅限于那些情况。

这是另一篇有用的帖子:https://stackoverflow.com/a/25087273/122718