ASP.NET MVC中的异步操作使用.NET 4上的ThreadPool中的线程

在这个问题之后,在ASP.NET MVC中使用异步操作时,它让我感觉很舒服。 所以,我写了两篇博文:

  • 我在C#5.0和ASP.NET MVC Web应用程序中采用基于任务的异步编程
  • 使用ASP.NET MVC 4中的基于任务的异步编程模型(TAP)调用异步数据库
  • 我对ASP.NET MVC上的异步操作有太多的误解。

    我总是听到这句话: 如果操作异步运行,应用程序可以更好地扩展

    而且我也听到过很多这样的句子: 如果流量很大,最好不要异步执行查询 - 消耗2个额外的线程来处理一个请求,将资源从其他请求中分离出来。

    我认为这两句话是不一致的。

    我没有关于线程池如何在ASP.NET上工作的很多信息,但我知道线程池的线程大小是有限的。 所以,第二句话必须与这个问题有关。

    我想知道ASP.NET MVC中的异步操作是否使用.NET 4上的ThreadPool的线程?

    例如,当我们实现一个AsyncController时,应用程序结构如何? 如果我的流量很大,实施AsyncController是个好主意吗?

    有没有人可以把这个黑色的窗帘放在我眼前,向我解释有关ASP.NET MVC 3(NET 4)上的异步处理?

    编辑:

    我已经阅读了下面的这个文档近百次,并且我理解主要的协议,但是我仍然有困惑,因为那里有太多不一致的评论。

    在ASP.NET MVC中使用异步控制器

    编辑:

    假设我有像下面那样的控制器动作(不是AsyncController的实现):

    public ViewResult Index() { 
    
        Task.Factory.StartNew(() => { 
            //Do an advanced looging here which takes a while
        });
    
        return View();
    }
    

    正如你在这里看到的,我发起了一次手术并忘掉了它。 然后,我立即返回而无需等待它完成。

    在这种情况下,它是否必须使用线程池中的线程? 如果是这样,完成后,该线程会发生什么? GC完成后是否进入并清理?

    编辑:

    对于@ Darin的回答,下面是一个与数据库交谈的异步代码示例:

    public class FooController : AsyncController {
    
        //EF 4.2 DbContext instance
        MyContext _context = new MyContext();
    
        public void IndexAsync() { 
    
            AsyncManager.OutstandingOperations.Increment(3);
    
            Task<IEnumerable<Foo>>.Factory.StartNew(() => { 
    
               return 
                    _context.Foos;
            }).ContinueWith(t => {
    
                AsyncManager.Parameters["foos"] = t.Result;
                AsyncManager.OutstandingOperations.Decrement();
            });
    
            Task<IEnumerable<Bars>>.Factory.StartNew(() => { 
    
               return 
                    _context.Bars;
            }).ContinueWith(t => {
    
                AsyncManager.Parameters["bars"] = t.Result;
                AsyncManager.OutstandingOperations.Decrement();
            });
    
            Task<IEnumerable<FooBar>>.Factory.StartNew(() => { 
    
               return 
                    _context.FooBars;
            }).ContinueWith(t => {
    
                AsyncManager.Parameters["foobars"] = t.Result;
                AsyncManager.OutstandingOperations.Decrement();
            });
        }
    
        public ViewResult IndexCompleted(
            IEnumerable<Foo> foos, 
            IEnumerable<Bar> bars,
            IEnumerable<FooBar> foobars) {
    
            //Do the regular stuff and return
    
        }
    }
    

    这里有一篇很好的文章,我建议您阅读以更好地理解ASP.NET中的异步处理(这是异步控制器基本表示的内容)。

    我们首先考虑一个标准的同步操作:

    public ActionResult Index()
    {
        // some processing
        return View();
    }
    

    当对此操作发出请求时,将从线程池中提取线程,并在此线程上执行此操作的主体。 因此,如果此操作内的处理速度较慢,则会阻止此线程进行整个处理,因此此线程无法重用来处理其他请求。 在请求执行结束时,线程返回到线程池。

    现在让我们举一个异步模式的例子:

    public void IndexAsync()
    {
        // perform some processing
    }
    
    public ActionResult IndexCompleted(object result)
    {
        return View();
    }
    

    向Index操作发送请求时,会从线程池中抽取一个线程,并执行IndexAsync方法的主体。 一旦该方法的主体完成执行,该线程就返回到线程池。 然后,使用标准的AsyncManager.OutstandingOperations ,一旦您发出完成异步操作的信号,就从线程池中提取另一个线程,并在其上执行IndexCompleted操作的主体并将结果呈现给客户端。

    因此,我们在这种模式中可以看到,单个客户端HTTP请求可以由两个不同的线程执行。

    现在,有趣的部分发生在IndexAsync方法内部。 如果你有一个阻塞操作,你完全在浪费异步控制器的全部目的,因为你阻塞了工作线程(请记住,这个动作的主体是在从线程池抽取的线程上执行的)。

    那么我们什么时候可以真正利用异步控制器,你可能会问?

    恕我直言,当我们有I / O密集型操作时(例如对远程服务的数据库和网络调用),我们可以获得最多的收益。 如果你有一个CPU密集型操作,异步操作不会给你带来太多好处。

    那么,为什么我们可以从I / O密集型操作中获益呢? 因为我们可以使用I / O完成端口。 IOCP功能非常强大,因为在执行整个操作期间,您不会占用服务器上的任何线程或资源。

    他们如何工作?

    假设我们想要使用WebClient.DownloadStringAsync方法下载远程网页的内容。 你调用这个方法将在操作系统中注册一个IOCP并立即返回。 在处理整个请求的过程中,服务器上不会消耗线程。 一切都在远程服务器上进行。 这可能需要很长时间,但您并不在乎,因为您不会危害您的工作线程。 一旦收到响应,就会发出IOCP信号,从线程池中抽取线程,并在该线程上执行回调。 但正如你所看到的,在整个过程中,我们并没有垄断任何线索。

    FileStream.BeginRead,SqlCommand.BeginExecute等方法也是如此。

    并行化多个数据库调用呢? 假设您有一个同步控制器操作,其中按顺序执行了4个阻止数据库调用。 很容易计算出,如果每个数据库调用需要200ms,那么您的控制器操作将需要大约800ms执行。

    如果您不需要按顺序运行这些调用,将它们并行化可以提高性能?

    这是一个很大的问题,不容易回答。 也许是,也许不是。 它完全取决于你如何实现这些数据库调用。 如果您像前面讨论的那样使用异步控制器和I / O完成端口,那么您将提升此控制器操作的性能以及其他操作的性能,因为您不会独占工作线程。

    另一方面,如果你实现它们效果不佳(在线程池中的线程上执行阻塞数据库调用),则基本上将执行此操作的总时间降低到大约200毫秒,但是您将消耗4个工作线程,可能会降低其他请求的性能,这些请求可能因池中缺少线程来处理它们而变得匮乏。

    所以这是非常困难的,如果您没有准备好对您的应用程序进行大量测试,请不要实施异步控制器,因为您有可能会造成更多的损失而不是收益。 只有在有理由这样做的情况下才能实现它们:例如,您已经确定标准同步控制器操作对于您的应用程序来说是一个瓶颈(在进行大量负载测试和测量之后)。

    现在让我们考虑一下你的例子:

    public ViewResult Index() { 
    
        Task.Factory.StartNew(() => { 
            //Do an advanced looging here which takes a while
        });
    
        return View();
    }
    

    当接收到索引操作的请求时,将从线程池中提取线程以执行其主体,但其主体仅使用TPL调度新任务。 所以动作执行结束并且线程返回到线程池。 除此之外,TPL使用线程池中的线程来执行它们的处理。 因此,即使原始线程返回到线程池,您也从该池抽取了另一个线程来执行任务的主体。 所以你已经危害了宝贵游泳池中的2条线。

    现在让我们考虑以下几点:

    public ViewResult Index() { 
    
        new Thread(() => { 
            //Do an advanced looging here which takes a while
        }).Start();
    
        return View();
    }
    

    在这种情况下,我们手动产生一个线程。 在这种情况下,Index操作主体的执行可能会稍微延长(因为产生一个新线程比从现有池中抽取更昂贵)。 但是高级日志记录操作的执行将在不属于池的线程上完成。 所以我们不会损害游泳池中的线索,这些线索可以免费为另一个请求服务。


    是的 - 所有线程都来自线程池。 您的MVC应用程序已经是多线程的,当请求进入新线程时,将从该池取出并用于为请求提供服务。 该线程将被锁定(来自其他请求),直到请求被完全服务并完成。 如果池中没有可用线程,则请求将不得不等待,直到有可用线程可用。

    如果你有异步控制器,他们仍然从池中获得一个线程,但是在服务请求时,他们可以放弃线程,等待发生某些事情(并且该线程可以被赋予另一个请求),并且当原始请求需要一个线程它再次从池中获得一个。

    不同之处在于,如果您有很多长时间运行的请求(线程正在等待某个响应),那么您可能会用尽池中的线程来处理基本请求。 如果您有异步控制器,则不会有更多线程,但正在等待的线程会返回到池并可处理其他请求。

    一个近乎真实的生活的例子......想想就像上公共汽车一样,有五个人等着上车,第一个上车,付款并坐下(司机为他们的请求提供服务),然后继续(司机正在服务你的要求),但你找不到你的钱; 当你在口袋里摸索时,司机会放弃你,并让下两个人(服务他们的请求),当你发现你的钱司机再次与你打交道(完成你的请求) - 第五人必须等到你已经完成,但第三和第四个人得到了服务,而你是中途获得服务。 这意味着驾驶员是游泳池中的唯一线程,乘客是请求。 如果有两名车手写下它的工作方式太复杂了,但你可以想象......

    如果没有一个异步控制器,你身后的乘客将不得不等待很长时间,而你寻找你的钱,同时公交车司机不会做任何工作。

    所以得出的结论是,如果很多人不知道他们的钱在哪里(即需要很长时间才能回应司机问过的问题),那么异步控制器可以帮助处理请求的吞吐量,从而加速某些过程。 如果没有aysnc控制器,每个人都会等待,直到前面的人已经完成处理。 但是不要忘记,在MVC中,你在一条总线上有很多总线驱动程序,所以异步不是自动选择。


    这里有两个概念。 首先,我们可以让我们的代码并行运行,以更快地执行或在另一个线程上安排代码,以避免让用户等待。 你有的例子

    public ViewResult Index() { 
    
        Task.Factory.StartNew(() => { 
            //Do an advanced looging here which takes a while
        });
    
        return View();
    }
    

    属于第二类。 用户将得到更快的响应,但服务器上的总工作量更高,因为它必须执行相同的工作+处理线程。

    另一个例子是:

    public ViewResult Index() { 
    
        Task.Factory.StartNew(() => { 
            //Make async web request to twitter with WebClient.DownloadString()
        });
    
        Task.Factory.StartNew(() => { 
            //Make async web request to facebook with WebClient.DownloadString()
        });
    
    
        //wait for both to be ready and merge the results
    
        return View();
    }
    

    由于这些请求并行运行,因此用户无需等待系列完成的时间。 但是你应该认识到,我们在这里使用的资源比在串口下运行更多,因为我们在多线程中运行代码,而我们也在等待线程。

    在客户端场景中这非常好。 在同一个新任务中包含同步的长时间运行的代码(在另一个线程上运行)也是很常见的,同样保持ui响应或者平行化以使其更快。 尽管如此,线程仍然在使用。 在高负载的服务器上,这可能会适得其反,因为您实际上使用的资源更多。 这是人们警告你的

    MVC中的异步控制器有另一个目标。 这里的要点是避免线程无所事事(这可能会影响可伸缩性)。 如果您所调用的API具有异步方法,那真的很重要。 像WebClient.DowloadStringAsync()一样。

    重点是你可以让你的线程返回来处理新的请求,直到Web请求完成,它会调用你的回调函数,它获得相同的或新的线程并完成请求。

    我希望你了解异步和并行之间的区别。 把并行代码想象成代码,你的线程坐在那里等待结果。 虽然异步代码是代码,当代码完成时您会收到通知,并且您可以重新开始工作,同时该线程可以完成其他工作。

    链接地址: http://www.djcxy.com/p/55701.html

    上一篇: Do asynchronous operations in ASP.NET MVC use a thread from ThreadPool on .NET 4

    下一篇: ASP.Net MVC 3.0 Attribute [OutputCache]