使用Task :: Run()在单独的线程中运行整个HTTP操作方法有多糟糕?

时间:2014-11-09 14:28:43

标签: asp.net .net asynchronous asp.net-web-api c++-cli

我使用Microsoft的Web API在C ++ / CLI(不是我的选择)中编写Web服务。 Web API中的 lot 函数是异步的,但由于我使用的是C ++ / CLI,因此我无法获得C#或VB的异步/等待支持。因此,后备位置是使用ContinueWith()来安排继续委托,以便安全地读取异步任务的结果。

但是,由于C ++ / CLI也不支持内联匿名委托或托管lambda,因此每个委托连续都必须作为单独的函数编写。很快就会变成带有Web API中异步函数数量的spaghetti。

所以,为了避免Task<T>::Result的{​​{3}},我一直在尝试这个:

[HttpGet, Route( "get/some/dto" )]
Task< SomeDTO ^ > ^ MyActionMethod()
{
  return Task::Run( gcnew Func< SomeDTO ^ >( this, &MyController::MyActionMethod2 ) );
}
SomeDTO ^ MyActionMethod2()
{
  // execute code and use any task->Result calls I need without deadlocking
}

好的,所以我知道这不是很好,但有多糟糕?我还没有充分了解Web API或ASP.NET的内容,以理解这将产生的性能或扩展分支。

此外,还有哪些其他后果可能与性能无关?例如,异常包含在额外的AggregateException中,这表示额外的复杂性和处理异常的工作。

2 个答案:

答案 0 :(得分:2)

您的内存使用量会随着应用程序的并行性而增加。对于MyActionMethod的每个并发调用,您将需要一个具有自己的堆栈的单独线程。对于每个并发呼叫,这将花费您大约1 MB的RAM。如果MyActionMethod运行的时间足够长,以便一次运行10000个实例,那么您需要查看10 GB的RAM。设置每个线程也有CPU开销。

如果并发性低,丢弃异步支持不会成为问题。在这种情况下,不要打扰Task::Run。只需更改MyActionMethod即可返回SomeDTO^(无Task包装)。

另一个潜在的担忧是忘记使用取消令牌。但是,对于Web API,通常可以让异常传播回Web API,最终取消同步调用。

最后,如果您计划并行执行操作方法中的任何操作,您仍需要使用ContinueWith来完成此操作。默认情况下,非同步意味着您一次只能执行一个操作。幸运的是,这样做通常都很好。

答案 1 :(得分:1)

  

好的,所以我知道这不是很好,但有多糟糕?

如果不对您的特定方案进行负载测试,很难回答这个问题。但是你可以浏览已知的语义(主要是from my blog)。

首先,当请求进入时,ASP.NET在该请求上下文中的线程池线程上执行您的处理程序。您的请求处理程序调用{​​{1}},它从线程池中获取另一个线程并在其上执行实际的请求逻辑。然后处理程序返回从Task.Run返回的任务;这会将原始请求线程释放回线程池。

然后,Task.Run委托将阻止任何异步部分。因此,此模式具有常规同步处理程序的扩展缺点,以及额外的线程上下文切换。此外,它使用来自ASP.NET线程池的线程,这不一定是坏事,但在某些情况下,它可能会抛弃ASP.NET线程池启发式。

  

此外,这可能带来的其他后果与性能无关?例如,异常包含在额外的AggregateException中,这表示额外的复杂性和处理异常的工作。

是的,来自任何Task.Run.Result来电的例外情况都将包含在Wait()中。您可以通过调用AggregateException来避免这种情况。

另一个重要的考虑因素是.GetAwaiter().GetResult()内执行的代码正在执行而没有请求上下文。因此,不会正确设置Task.Run,当前文化,线程主体等环境数据。在调用HttpContext.Current之前,您必须捕获所有重要数据并手动将其传递下来。