IT

ASP.NET MVC에서 비동기 작업을 수행하려면 .NET 4의 ThreadPool에서 스레드를 사용하십시오.

lottoking 2020. 6. 5. 08:15
반응형

ASP.NET MVC에서 비동기 작업을 수행하려면 .NET 4의 ThreadPool에서 스레드를 사용하십시오.


이 질문 후에 ASP.NET MVC에서 비동기 작업을 사용할 때 편안합니다. 그래서 나는 그것에 대해 두 개의 블로그 게시물을 썼습니다.

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메소드 본문 이 실행됩니다. 이 메소드의 본문이 실행을 마치면 스레드가 스레드 풀로 리턴됩니다. 그런 다음 standard를 사용하여 AsyncManager.OutstandingOperations비동기 작업의 완료를 알리면 스레드 풀에서 다른 스레드를 가져 IndexCompleted와서 작업 본문 이 실행되고 결과가 클라이언트에 렌더링됩니다.

따라서이 패턴에서 볼 수있는 것은 단일 클라이언트 HTTP 요청이 두 개의 다른 스레드에서 실행될 수 있다는 것입니다.

이제 흥미로운 부분이 IndexAsync메서드 내부에서 발생합니다 . 내부에 차단 작업이있는 경우 작업자 스레드를 차단하기 때문에 비동기 컨트롤러의 전체 목적을 완전히 낭비하는 것입니다 (이 작업의 본문은 스레드 풀에서 가져온 스레드에서 실행됨을 기억하십시오).

그렇다면 언제 비동기 컨트롤러를 활용할 수 있습니까?

IMHO는 I / O 집약적 인 작업 (예 : 데이터베이스 및 원격 서비스에 대한 네트워크 호출)이있을 때 가장 많이 얻을 수 있습니다. CPU 집약적 인 작업이있는 경우 비동기 작업으로 많은 이점을 얻지 못합니다.

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.

How do they work?

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.

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

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?

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.

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();
}

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();
}

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.


Yes - all threads come from the thread-pool. Your MVC app is already multi-threaded, when a request comes in a new thread will be taken from the pool and used to service the request. That thread will be 'locked' (from other requests) until the request is fully serviced and completed. If there is no thread available in the pool the request will have to wait until one is available.

If you have async controllers they still get a thread from the pool but while servicing the request they can give up the thread, while waiting for something to happen (and that thread can be given to another request) and when the original request needs a thread again it gets one from the pool.

The difference is that if you have a lot of long-running requests (where the thread is waiting for a response from something) you might run out of threads from the the pool to service even basic requests. If you have async controllers, you don't have any more threads but those threads that are waiting are returned to the pool and can service other requests.

A nearly real life example... Think of it like getting on a bus, there's five people waiting to get on, the first gets on, pays and sits down (the driver serviced their request), you get on (the driver is servicing your request) but you can't find your money; as you fumble in your pockets the driver gives up on you and gets the next two people on (servicing their requests), when you find your money the driver starts dealing with you again (completing your request) - the fifth person has to wait until you are done but the third and fourth people got served while you were half way through getting served. This means that the driver is the one and only thread from the pool and the passengers are the requests. It was too complicated to write how it would work if there was two drivers but you can imagine...

Without an async controller, the passengers behind you would have to wait ages while you looked for your money, meanwhile the bus driver would be doing no work.

So the conclusion is, if lots of people don't know where their money is (i.e. require a long time to respond to something the driver has asked) async controllers could well help throughput of requests, speeding up the process from some. Without an aysnc controller everyone waits until the person in front has been completely dealt with. BUT don't forget that in MVC you have a lot of bus drivers on a single bus so async is not an automatic choice.


There are two concepts at play here. First of all we can make our code run in parallel to execute faster or schedule code on another thread to avoid making the user wait. The example you had

public ViewResult Index() { 

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

    return View();
}

belongs to the second category. The user will get a faster response but the total workload on the server is higher because it has to do the same work + handle the threading.

Another example of this would be:

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();
}

Because the requests run in parallel the user won't have to wait as long as if they where done in serial. But you should realize that we use up more resources here than if we ran in serial because we run the code at many threads while we have on thread waiting too.

This is perfectly fine in a client scenario. And it is quite common there to wrap synchronous long running code in a new task(run it on another thread) too keep the ui responsive or parallize to make it faster. A thread is still used for the whole duration though. On a server with high load this could backfire because you actually use more resources. This is what people have warned you about

Async controllers in MVC has another goal though. The point here is to avoid having threads sittings around doing nothing(which can hurt scalability). It really only matters if the API's you are calling have async methods. Like WebClient.DowloadStringAsync().

The point is that you can let your thread be returned to handle new requests untill the web request is finished where it will call you callback which gets the same or a new thread and finish the request.

I hope you understand the difference between asynchronous and parallel. Think of parallel code as code where your thread sits around and wait for the result. While asynchronous code is code where you will be notified when the code is done and you can get back working at it, in the meantime the thread can do other work.


Applications can scale better if operations run asynchronously, but only if there are resources available to service the additional operations.

Asynchronous operations ensure that you're never blocking an action because an existing one is in progress. ASP.NET has an asynchronous model that allows multiple requests to execute side-by-side. It would be possible to queue the requests up and processes them FIFO, but this would not scale well when you have hundreds of requests queued up and each request takes 100ms to process.

If you have a huge volume of traffic, you may be better off not performing your queries asynchronously, as there may be no additional resources to service the requests. If there are no spare resources, your requests are forced to queue up, take exponentially longer or outright fail, in which case the asynchronous overhead (mutexes and context-switching operations) isn't giving you anything.

As far as ASP.NET goes, you don't have a choice - it's uses an asynchronous model, because that's what makes sense for the server-client model. If you were to be writing your own code internally that uses an async pattern to attempt to scale better, unless you're trying to manage a resource that's shared between all requests, you won't actually see any improvements because they're already wrapped in an asynchronous process that doesn't block anything else.

Ultimately, it's all subjective until you actually look at what's causing a bottleneck in your system. Sometimes it's obvious where an asynchronous pattern will help (by preventing a queued resource blocking). Ultimately only measuring and analysing a system can indicate where you can gain efficiencies.

Edit:

In your example, the Task.Factory.StartNew call will queue up an operation on the .NET thread-pool. The nature of Thread Pool threads is to be re-used (to avoid the cost of creating/destroying lots of threads). Once the operation completes, the thread is released back to the pool to be re-used by another request (the Garbage Collector doesn't actually get involved unless you created some objects in your operations, in which case they're collected as per normal scoping).

As far as ASP.NET is concerned, there is no special operation here. The ASP.NET request completes without respect to the asynchronous task. The only concern might be if your thread pool is saturated (i.e. there are no threads available to service the request right now and the pool's settings don't allow more threads to be created), in which case the request is blocked waiting to start the task until a pool thread becomes available.


Yes, they use a thread from the thread pool. There is actually a pretty excellent guide from MSDN that will tackle all of your questions and more. I have found it to be quite useful in the past. Check it out!

http://msdn.microsoft.com/en-us/library/ee728598.aspx

Meanwhile, the comments + suggestions that you hear about asynchronous code should be taken with a grain of salt. For starters, just making something async doesn't necessarily make it scale better, and in some cases can make your application scale worse. The other comment you posted about "a huge volume of traffic..." is also only correct in certain contexts. It really depends on what your operations are doing, and how they interact with other parts of the system.

In short, lots of people have lots of opinions about async, but they may not be correct out of context. I'd say focus on your exact problems, and do basic performance testing to see what async controllers, etc. actually handle with your application.


First thing its not MVC but the IIS who maintains the thread pool. So any request which comes to MVC or ASP.NET application is served from threads which are maintained in thread pool. Only with making the app Asynch he invokes this action in a different thread and releases the thread immediately so that other requests can be taken.

I have explained the same with a detail video (http://www.youtube.com/watch?v=wvg13n5V0V0/ "MVC Asynch controllers and thread starvation" ) which shows how thread starvation happens in MVC and how its minimized by using MVC Asynch controllers.I also have measured the request queues using perfmon so that you can see how request queues are decreased for MVC asynch and how its worst for Synch operations.

참고URL : https://stackoverflow.com/questions/8743067/do-asynchronous-operations-in-asp-net-mvc-use-a-thread-from-threadpool-on-net-4

반응형