在 ASP.NET MVC 中执行异步操作使用来自 .NET 4 上的 ThreadPool 的线程 [英] Do asynchronous operations in ASP.NET MVC use a thread from ThreadPool on .NET 4

查看:24
本文介绍了在 ASP.NET MVC 中执行异步操作使用来自 .NET 4 上的 ThreadPool 的线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

After this question, it makes me comfortable when using async operations in ASP.NET MVC. So, I wrote two blog posts on that:

我对 ASP.NET MVC 上的异步操作有太多误解.

I have too many misunderstandings in my mind about asynchronous operations on ASP.NET MVC.

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

I always hear this sentence: Application can scale better if operations run asynchronously

我也经常听到这样的句子:如果您的流量很大,最好不要异步执行查询 - 消耗 2 个额外线程来处理一个请求需要花费资源远离其他传入请求.

And I heard this kind of sentences a lot as well: if you have a huge volume of traffic, you may be better off not performing your queries asynchronously - consuming 2 extra threads to service one request takes resources away from other incoming requests.

我认为这两个句子不一致.

I think those two sentences are inconsistent.

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

I do not have much information about how threadpool works on ASP.NET but I know that threadpool has a limited size for threads. So, the second sentence has to be related to this issue.

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

And I would like to know if asynchronous operations in ASP.NET MVC uses a thread from ThreadPool on .NET 4?

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

For example, when we implement a AsyncController, how does the app structures? If I get huge traffic, is it a good idea to implement AsyncController?

有没有人可以在我眼前揭开这个黑幕并向我解释有关 ASP.NET MVC 3 (NET 4) 异步的交易?

Is there anybody out there who can take this black curtain away in front of my eyes and explain me the deal about asynchrony on ASP.NET MVC 3 (NET 4)?

我已阅读以下文档近数百次,我了解主要内容,但我仍然感到困惑,因为那里有太多不一致的评论.

I have read this below document nearly hundreds of times and I understand the main deal but still I have confusion because there are too much inconsistent comment out there.

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

假设我有如下控制器动作(虽然不是 AsyncController 的实现):

Let's assume I have controller action like below (not an implementation of AsyncController though):

public ViewResult Index() { 

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

    return View();
}

正如你在这里看到的,我启动了一个操作并忘记了它.然后,我立即返回而不等待它完成.

As you see here, I fire an operation and forget about it. Then, I return immediately without waiting it be completed.

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

In this case, does this have to use a thread from threadpool? If so, after it completes, what happens to that thread? Does GC comes in and clean up just after it completes?

对于@Darin 的回答,这里是一个与数据库对话的异步代码示例:

For the @Darin's answer, here is a sample of async code which talks to database:

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 中的异步处理(异步控制器基本上代表什么).

Here's an excellent article I would recommend you reading to better understand asynchronous processing in ASP.NET (which is what asynchronous controllers basically represent).

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

Let's first consider a standard synchronous action:

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

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

When a request is made to this action a thread is drawn from the thread pool and the body of this action is executed on this thread. So if the processing inside this action is slow you are blocking this thread for the entire processing, so this thread cannot be reused to process other requests. At the end of the request execution, the thread is returned to the thread pool.

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

Now let's take an example of the asynchronous pattern:

public void IndexAsync()
{
    // perform some processing
}

public ActionResult IndexCompleted(object result)
{
    return View();
}

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

When a request is sent to the Index action, a thread is drawn from the thread pool and the body of the IndexAsync method is executed. Once the body of this method finishes executing, the thread is returned to the thread pool. Then, using the standard AsyncManager.OutstandingOperations, once you signal the completion of the async operation, another thread is drawn from the thread pool and the body of the IndexCompleted action is executed on it and the result rendered to the client.

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

So what we can see in this pattern is that a single client HTTP request could be executed by two different threads.

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

Now the interesting part happens inside the IndexAsync method. If you have a blocking operation inside it, you are totally wasting the whole purpose of the asynchronous controllers because you are blocking the worker thread (remember that the body of this action is executed on a thread drawn from the thread pool).

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

So when can we take real advantage of asynchronous controllers you might ask?

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

IMHO we can gain most when we have I/O intensive operations (such as database and network calls to remote services). If you have a CPU intensive operation, asynchronous actions won't bring you much benefit.

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

So why can we gain benefit from I/O intensive operations? Because we could use I/O Completion Ports. IOCP are extremely powerful because you do not consume any threads or resources on the server during the execution of the entire operation.

它们是如何工作的?

假设我们要使用 WebClient.DownloadStringAsync 方法.您调用此方法将在操作系统中注册一个 IOCP 并立即返回.在整个请求的处理过程中,您的服务器上不会消耗任何线程.一切都发生在远程服务器上.这可能需要很多时间,但您不在乎,因为您不会危及您的工作线程.一旦收到响应,就会发出 IOCP 信号,从线程池中抽取一个线程并在该线程上执行回调.但是大家可以看到,在整个过程中,我们并没有垄断任何线程.

Suppose that we want to download the contents of a remote web page using the WebClient.DownloadStringAsync method. You call this method which will register an IOCP within the operating system and return immediately. During the processing of the entire request, no threads are consumed on your server. Everything happens on the remote server. This could take lots of time but you don't care as you are not jeopardizing your worker threads. Once a response is received the IOCP is signaled, a thread is drawn from the thread pool and the callback is executed on this thread. But as you can see, during the entire process, we have not monopolized any threads.

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

The same stands true with methods such as FileStream.BeginRead, SqlCommand.BeginExecute, ...

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

What about parallelizing multiple database calls? Suppose that you had a synchronous controller action in which you performed 4 blocking database calls in sequence. It's easy to calculate that if each database call takes 200ms, your controller action will take roughly 800ms to execute.

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

If you don't need to run those calls sequentially, would parallelizing them improve performance?

这是个大问题,不容易回答.也许是,也许不是.这将完全取决于您如何实现这些数据库调用.如果您如前所述使用异步控制器和 I/O 完成端口,您将提高此控制器操作和其他操作的性能,因为您不会独占工作线程.

That's the big question, which is not easy to answer. Maybe yes, maybe no. It will entirely depend on how you implement those database calls. If you use async controllers and I/O Completion Ports as discussed previously you will boost the performance of this controller action and of other actions as well, as you won't be monopolizing worker threads.

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

On the other hand if you implement them poorly (with a blocking database call performed on a thread from the thread pool), you will basically lower the total time of execution of this action to roughly 200ms but you would have consumed 4 worker threads so you might have degraded the performance of other requests which might become starving because of missing threads in the pool to process them.

因此这是非常困难的,如果您还没有准备好对您的应用程序进行广泛的测试,请不要实现异步控制器,因为这样做可能会造成更多的损失而不是收益.仅当您有理由这样做时才实施它们:例如,您已经确定标准同步控制器操作是您的应用程序的瓶颈(当然是在执行大量负载测试和测量之后).

So it is very difficult and if you don't feel ready to perform extensive tests on your application, do not implement asynchronous controllers, as chances are that you will do more damage than benefit. Implement them only if you have a reason to do so: for example you have identified that standard synchronous controller actions are a bottleneck to your application (after performing extensive load tests and measurements of course).

现在让我们考虑一下您的示例:

Now let's consider your example:

public ViewResult Index() { 

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

    return View();
}

当收到对 Index 操作的请求时,从线程池中抽取一个线程来执行其主体,但其主体仅使用 TPL.于是动作执行结束,线程返回线程池.除此之外,TPL 使用来自线程池来执行它们的处理.所以即使原来的线程返回到线程池中,你也从这个池中抽取了另一个线程来执行任务的主体.因此,您从宝贵的池中危及了 2 个线程.

When a request is received for the Index action a thread is drawn from the thread pool to execute its body, but its body only schedules a new task using TPL. So the action execution ends and the thread is returned to the thread pool. Except that, TPL uses threads from the thread pool to perform their processing. So even if the original thread was returned to the thread pool, you have drawn another thread from this pool to execute the body of the task. So you have jeopardized 2 threads from your precious pool.

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

Now let's consider the following:

public ViewResult Index() { 

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

    return View();
}

在这种情况下,我们手动生成一个线程.在这种情况下,执行 Index 动作的主体可能需要稍长的时间(因为生成一个新线程比从现有池中提取一个线程更昂贵).但是高级日志操作的执行将在不属于池的线程上完成.因此,我们不会危及池中的线程,这些线程仍然可以免费用于服务另一个请求.

In this case we are manually spawning a thread. In this case the execution of the body of the Index action might take slightly longer (because spawning a new thread is more expensive than drawing one from an existing pool). But the execution of the advanced logging operation will be done on a thread which is not part of the pool. So we are not jeopardizing threads from the pool which remain free for serving another requests.

这篇关于在 ASP.NET MVC 中执行异步操作使用来自 .NET 4 上的 ThreadPool 的线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆