我使用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中,这表示额外的复杂性和处理异常的工作。
答案 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
之前,您必须捕获所有重要数据并手动将其传递下来。