使用AsyncController帮助增加旧版ASP.NET MVC 3项目的并发性

Din*_*one 0 asp.net-mvc multithreading asynccontroller

我们现在有一个与并发用户斗争的网站.

以下是该项目的高级背景:

  • 旧版ASP.NET MVC 3项目(.NET 4)
  • 无法对核心代码进行任何重大改写
  • 占用时间最长的主要入口点是控制器SubmitSearch上的操作Search.平均响应时间为5-10秒.

因此,如第二点所述,我们不希望在此项目上花费太多时间来重写大部分内容.但是,我们希望尝试增加并发用户.我们不打算改变其他任何东西或提高性能,因为它需要更多的工作.

我们看到的是,随着越来越多的人受到打击SubmitSearch,网站总体上变慢了.这很可能是由于所有IIS线程被锁定执行搜索.

我们正在寻求实现AsyncControllerSubmitSearch在正常的CLR线程上执行操作.以下是我们想要实现它的方式:

假设这是原始SubmitSearch方法:

/// <summary>
/// Submits a search for execution.
/// </summary>
/// <param name="searchData">The search data</param>
/// <returns></returns>
public virtual ActionResult SubmitSearch(SearchFormModel searchData)
{
    //our search code
}
Run Code Online (Sandbox Code Playgroud)

我们希望转换为最快的方法AsyncController是简单地执行此操作:

/// <summary>
/// Submits a search for execution.
/// </summary>
/// <param name="searchData">The search data</param>
/// <returns></returns>
protected virtual ActionResult SubmitSearch(SearchFormModel searchData)
{
    //our search code
}

/// <summary>
/// Asynchronous Search entry point
/// </summary>
/// <param name="searchData"></param>
public void SubmitSearchAsync(SearchFormModel searchData)
{
    AsyncManager.OutstandingOperations.Increment();
    System.Threading.Tasks.Task.Factory.StartNew(() =>
    {
        ActionResult result = SubmitSearch(searchData);
        AsyncManager.Parameters["result"] = result;
        AsyncManager.OutstandingOperations.Decrement();
    });

    return;
}

/// <summary>
/// Called when the asynchronous search has completed
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
public ActionResult SubmitSearchCompleted(ActionResult result)
{
    //Just return the action result
    return result;
}
Run Code Online (Sandbox Code Playgroud)

当然这不起作用,因为我们所引用的代码全部通过HttpContext.Current,我们知道最终会null采用这种方法.

所以我们希望这样做SubmitSearchAsync:

/// <summary>
/// Asynchronous Search entry point
/// </summary>
/// <param name="searchData"></param>
public void SubmitSearchAsync(SearchFormModel searchData)
{
    AsyncManager.OutstandingOperations.Increment();
    System.Threading.Tasks.Task.Factory.StartNew(() =>
    {
        ActionResult result = null;
        AsyncManager.Sync(() =>
        {
            result = SubmitSearch(searchData);
        });

        AsyncManager.Parameters["result"] = result;
        AsyncManager.OutstandingOperations.Decrement();
    });

    return;
}
Run Code Online (Sandbox Code Playgroud)

这解决了这个问题.

因此,这里是我的关注:
是否包裹的执行SubmitSearchAsyncManager.Sync方法破坏使用这种模式的目的是什么?换句话说,当我们在AsyncManager.Sync方法中时,我们是否会回到IIS线程,这会让我们回到原点?

谢谢

Aar*_*ght 5

包装是否在执行SubmitSearchAsyncManager.Sync方法破坏使用这种模式的目的是什么?换句话说,当我们在AsyncManager.Sync方法中时,我们是否会回到IIS线程,这会让我们回到原点?

或多或少,是的.但不幸的是,在您的情况下,使用Task.Factory.StartNew 也会失败使用异步控制器的目的.通过您尝试使用的方法,您无法获胜.

IIS线程,启动的线程ThreadPool.QueueUserWorkItemTask线程都来自同一个线程池.

为了从异步控制器中获得任何好处,您需要真正的异步方法.换句话说,像Stream.ReadAsyncWebRequest.GetResponseAsync这样的方法.这些特别命名的方法使用I/O完成端口而不是普通线程,它们使用硬件中断并在不同的线程池上运行.

我在很久以前的回答中写过这篇文章:在高流量场景中使用ASP.NET中的ThreadPool.QueueUserWorkItem.任务和等待者都非常甜蜜,但它们并没有改变.NET线程池的基本动态.

需要注意的一点是,有一个选项,TaskCreationOptions.LongRunning,您可以在创建时指定Task,这实际上通知框架该任务将进行大量等待,理论上TPL将尝试避免调度它在线程池中.实际上,这在高流量网站上可能不太实用,因为:

  1. 该框架实际上并不保证它不会使用线程池.这是一个实现细节,该选项只是您提供的提示.

  2. 即使它确实避免了池,它仍然需要使用一个线程,这基本上就像使用new Thread- 如果不是字面上,那么至少有效.这意味着繁重的上下文切换,这绝对会影响性能,并且是首先存在线程池的主要原因.

"搜索"命令清楚地暗示某种I/O,这意味着可能存在一种可以在某处使用的真正异步方法,即使它是旧式BeginXyz/ EndXyz.这里没有快捷方式,没有快速修复; 你必须重新设计代码才能真正实现异步.

.NET框架无法检查您内部发生的情况Task并将其神奇地转换为中断.除非直接引用了解它们的特定方法,否则它根本无法使用I/O完成端口.

您正在使用的下一个Web或中间件应用程序,请尝试提前考虑这一点并避免像瘟疫这样的同步I/O.