Thực hiện các hoạt động không đồng bộ trong ASP.NET MVC sử dụng một luồng từ ThreadPool trên .NET 4


158

Sau câu hỏi này, nó làm tôi thoải mái khi sử dụng các hoạt động async trong ASP.NET MVC. Vì vậy, tôi đã viết hai bài đăng trên blog về điều đó:

Tôi có quá nhiều hiểu lầm trong đầu về các hoạt động không đồng bộ trên ASP.NET MVC.

Tôi luôn nghe câu này: Ứng dụng có thể mở rộng tốt hơn nếu các hoạt động chạy không đồng bộ

Và tôi cũng đã nghe loại câu này rất nhiều: nếu bạn có lưu lượng lớn, bạn có thể không thực hiện các truy vấn của mình một cách không đồng bộ - tiêu thụ thêm 2 luồng cho dịch vụ, một yêu cầu sẽ lấy tài nguyên khỏi các yêu cầu đến khác.

Tôi nghĩ rằng hai câu đó không nhất quán.

Tôi không có nhiều thông tin về cách hoạt động của threadpool trên ASP.NET nhưng tôi biết rằng threadpool có kích thước giới hạn cho các luồng. Vì vậy, câu thứ hai phải liên quan đến vấn đề này.

Và tôi muốn biết nếu các hoạt động không đồng bộ trong ASP.NET MVC sử dụng một luồng từ ThreadPool trên .NET 4?

Ví dụ: khi chúng tôi triển khai AsyncControll, ứng dụng sẽ cấu trúc như thế nào? Nếu tôi nhận được lưu lượng truy cập lớn, đó có phải là một ý tưởng tốt để triển khai AsyncControll không?

Có ai ngoài kia có thể gỡ tấm màn đen này ra trước mắt tôi và giải thích cho tôi thỏa thuận về sự không đồng bộ trên ASP.NET MVC 3 (NET 4) không?

Biên tập:

Tôi đã đọc tài liệu dưới đây gần hàng trăm lần và tôi hiểu thỏa thuận chính nhưng tôi vẫn bối rối vì có quá nhiều bình luận không nhất quán ngoài kia.

Sử dụng bộ điều khiển không đồng bộ trong ASP.NET MVC

Biên tập:

Giả sử tôi có hành động điều khiển như bên dưới (không phải là triển khai AsyncControllermặc dù):

public ViewResult Index() { 

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

    return View();
}

Như bạn thấy ở đây, tôi bắn một hoạt động và quên nó đi. Sau đó, tôi trở lại ngay lập tức mà không chờ đợi nó được hoàn thành.

Trong trường hợp này, điều này có phải sử dụng một chủ đề từ threadpool không? Nếu vậy, sau khi nó hoàn thành, điều gì xảy ra với chủ đề đó? Có GCđến và dọn dẹp ngay sau khi nó hoàn thành?

Biên tập:

Đối với câu trả lời của @ Darin, đây là một mẫu mã async nói về cơ sở dữ liệu:

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

    }
}

Không chắc chắn về câu trả lời, nhưng đáng chú ý của nó không đồng bộ và đa luồng là những điều khác nhau. Vì vậy, có thể có một số luồng cố định với xử lý không đồng bộ. Điều gì sẽ xảy ra là khi một trang phải chặn để nói, I / O, một trang khác sẽ có cơ hội chạy trên cùng một chủ đề. Đó là cách cả hai câu lệnh đó có thể đúng, async có thể làm cho mọi thứ nhanh hơn nhưng quá nhiều chủ đề là một vấn đề.
Chris Chilvers

@ChrisChilvers Yep, đa luồng không phải lúc nào cũng cần thiết cho hoạt động không đồng bộ. Tôi đã tìm ra điều đó nhưng tôi nghĩ rằng tôi không có người điều khiển về điều đó như tôi có thể nói. AsyncControll xoay tròn bao nhiêu chủ đề mà nó muốn theo quan điểm của tôi nhưng cũng không chắc về điều đó. Có một khái niệm về threadpool trên các ứng dụng máy tính để bàn như WPF không? Tôi nghĩ rằng số lượng chủ đề không phải là một vấn đề trên các loại ứng dụng.
kéo co


Tôi nghĩ rằng vấn đề (và do đó không nhất quán) là câu lệnh thứ hai sử dụng không đồng bộ khi nó có nghĩa là nhiều luồng. Điều này có thể là do đó là cách asp.net đã triển khai các trang không đồng bộ và do đó việc triển khai cụ thể đã gây nhầm lẫn vấn đề (vì tên của tính năng gây ra sự cố sẽ là các trang không đồng bộ), nhưng tôi không chắc về ý nghĩa cụ thể . Vì vậy, chúng có nghĩa là "nhiều chủ đề" hoặc có nghĩa là "các trang không đồng bộ trong phiên bản asp.net X" vì các phiên bản trong tương lai có thể thay đổi việc triển khai. Hoặc họ chỉ có nghĩa là sử dụng nhóm luồng để thực hiện async trong một trang.
Chris Chilvers

@ChrisChilvers ơi, anh bạn! Tôi bối rối hơn sau những bình luận này: s
tugberk

Câu trả lời:


177

Đây là một bài viết tuyệt vời tôi khuyên bạn nên đọc để hiểu rõ hơn về xử lý không đồng bộ trong ASP.NET (đó là những gì bộ điều khiển không đồng bộ về cơ bản thể hiện).

Trước tiên chúng ta hãy xem xét một hành động đồng bộ tiêu chuẩn:

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

Khi một yêu cầu được thực hiện cho hành động này, một luồng được rút ra từ nhóm luồng và phần thân của hành động này được thực thi trên luồng này. Vì vậy, nếu quá trình xử lý bên trong hành động này bị chậm, bạn đang chặn luồng này cho toàn bộ quá trình xử lý, vì vậy luồng này không thể được sử dụng lại để xử lý các yêu cầu khác. Khi kết thúc thực hiện yêu cầu, luồng được trả về nhóm luồng.

Bây giờ hãy lấy một ví dụ về mẫu không đồng bộ:

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

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

Khi một yêu cầu được gửi đến hành động Index, một luồng được rút ra từ nhóm luồng và phần thân của IndexAsyncphương thức được thực thi. Khi phần thân của phương thức này kết thúc thực thi, luồng được trả về nhóm luồng. Sau đó, bằng cách sử dụng tiêu chuẩn AsyncManager.OutstandingOperations, một khi bạn báo hiệu việc hoàn thành thao tác async, một luồng khác được rút ra từ nhóm luồng và phần thân của IndexCompletedhành động được thực thi trên nó và kết quả được hiển thị cho máy khách.

Vì vậy, những gì chúng ta có thể thấy trong mẫu này là một yêu cầu HTTP của một máy khách có thể được thực thi bởi hai luồng khác nhau.

Bây giờ phần thú vị xảy ra bên trong IndexAsyncphương thức. Nếu bạn có một hoạt động chặn bên trong nó, bạn hoàn toàn lãng phí toàn bộ mục đích của các bộ điều khiển không đồng bộ vì bạn đang chặn luồng công nhân (hãy nhớ rằng phần thân của hành động này được thực thi trên một luồng được rút ra từ nhóm luồng).

Vậy khi nào chúng ta có thể tận dụng lợi thế thực sự của bộ điều khiển không đồng bộ mà bạn có thể yêu cầu?

IMHO chúng ta có thể đạt được hầu hết khi chúng ta có các hoạt động chuyên sâu I / O (như cơ sở dữ liệu và các cuộc gọi mạng đến các dịch vụ từ xa). Nếu bạn có một hoạt động chuyên sâu về CPU, các hành động không đồng bộ sẽ không mang lại cho bạn nhiều lợi ích.

Vậy tại sao chúng ta có thể đạt được lợi ích từ các hoạt động chuyên sâu I / O? Bởi vì chúng tôi có thể sử dụng cổng I / O hoàn thành . IOCP cực kỳ mạnh mẽ vì bạn không tiêu thụ bất kỳ luồng hoặc tài nguyên nào trên máy chủ trong quá trình thực thi toàn bộ hoạt động.

Họ làm việc như thế nào?

Giả sử rằng chúng tôi muốn tải xuống nội dung của một trang web từ xa bằng phương pháp WebClient.DoadStringAsync . Bạn gọi phương thức này sẽ đăng ký IOCP trong hệ điều hành và trả về ngay lập tức. Trong quá trình xử lý toàn bộ yêu cầu, không có luồng nào được sử dụng trên máy chủ của bạn. Mọi thứ xảy ra trên máy chủ từ xa. Điều này có thể mất nhiều thời gian nhưng bạn không quan tâm vì bạn không gây nguy hiểm cho chủ đề công nhân của bạn. Khi nhận được phản hồi, IOCP được báo hiệu, một luồng được rút ra từ nhóm luồng và cuộc gọi lại được thực hiện trên luồng này. Nhưng như bạn có thể thấy, trong toàn bộ quá trình, chúng tôi không độc quyền bất kỳ chủ đề nào.

Điều tương tự cũng đúng với các phương thức như FileStream.BeginRead, SqlCommand.BeginExecute, ...

Điều gì về việc song song nhiều cuộc gọi cơ sở dữ liệu? Giả sử rằng bạn đã có một hành động điều khiển đồng bộ trong đó bạn thực hiện 4 cuộc gọi cơ sở dữ liệu theo trình tự. Thật dễ dàng để tính toán rằng nếu mỗi cuộc gọi cơ sở dữ liệu mất 200ms, hành động điều khiển của bạn sẽ mất khoảng 800ms để thực thi.

Nếu bạn không cần phải thực hiện các cuộc gọi đó một cách tuần tự, liệu song song chúng có cải thiện hiệu suất không?

Đó là câu hỏi lớn, không dễ trả lời. Có thể có có thể không. Nó sẽ hoàn toàn phụ thuộc vào cách bạn thực hiện các cuộc gọi cơ sở dữ liệu đó. Nếu bạn sử dụng bộ điều khiển không đồng bộ và Cổng hoàn thành I / O như đã thảo luận trước đây, bạn sẽ tăng hiệu suất của hành động của bộ điều khiển này cũng như các hành động khác, vì bạn sẽ không độc quyền các luồng công nhân.

Mặt khác, nếu bạn triển khai chúng kém (với một cuộc gọi cơ sở dữ liệu chặn được thực hiện trên một luồng từ nhóm luồng), về cơ bản bạn sẽ giảm tổng thời gian thực hiện hành động này xuống khoảng 200ms nhưng bạn sẽ tiêu thụ 4 luồng công nhân nên bạn có thể đã làm giảm hiệu suất của các yêu cầu khác có thể trở nên đói vì thiếu các luồng trong nhóm để xử lý chúng.

Vì vậy, rất khó và nếu bạn không cảm thấy sẵn sàng thực hiện các thử nghiệm rộng rãi trên ứng dụng của mình, đừng thực hiện các bộ điều khiển không đồng bộ, vì rất có thể bạn sẽ gây thiệt hại nhiều hơn lợi ích. Chỉ triển khai chúng nếu bạn có lý do để làm như vậy: ví dụ: bạn đã xác định rằng các hành động của bộ điều khiển đồng bộ tiêu chuẩn là một nút cổ chai đối với ứng dụng của bạn (dĩ nhiên sau khi thực hiện các phép đo tải và đo lường mở rộng).

Bây giờ hãy xem xét ví dụ của bạn:

public ViewResult Index() { 

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

    return View();
}

Khi nhận được yêu cầu cho hành động Index, một luồng được rút ra từ nhóm luồng để thực thi phần thân của nó, nhưng phần thân của nó chỉ lên lịch cho một tác vụ mới sử dụng TPL . Vì vậy, việc thực hiện hành động kết thúc và luồng được trả về nhóm luồng. Ngoài ra, TPL sử dụng các luồng từ nhóm luồng để thực hiện xử lý. Vì vậy, ngay cả khi luồng gốc được trả về nhóm luồng, bạn đã rút một luồng khác từ nhóm này để thực thi phần thân của tác vụ. Vì vậy, bạn đã gây nguy hiểm cho 2 chủ đề từ hồ bơi quý giá của bạn.

Bây giờ hãy xem xét những điều sau đây:

public ViewResult Index() { 

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

    return View();
}

Trong trường hợp này, chúng tôi đang tự sinh ra một chủ đề. Trong trường hợp này, việc thực thi phần thân của hành động Index có thể mất nhiều thời gian hơn một chút (vì sinh ra một luồng mới sẽ tốn kém hơn so với việc vẽ một luồng từ một nhóm hiện có). Nhưng việc thực hiện thao tác ghi nhật ký nâng cao sẽ được thực hiện trên một luồng không phải là một phần của nhóm. Vì vậy, chúng tôi không gây nguy hiểm cho các chủ đề từ nhóm mà vẫn miễn phí để phục vụ các yêu cầu khác.


1
Thực sự chi tiết, cảm ơn! Giả sử rằng, chúng ta có 4 Nhiệm vụ không đồng bộ ( System.Threading.Task) đang chạy bên trong IndexAsyncphương thức. Trong các hoạt động đó, chúng tôi đang thực hiện các cuộc gọi db đến một máy chủ. Vì vậy, tất cả chúng đều hoạt động chuyên sâu I / O, phải không? Trong trường hợp đó, chúng ta có tạo 4 luồng riêng biệt (hoặc nhận 4 luồng riêng biệt từ nhóm luồng) không? Giả sử tôi có một máy đa lõi, họ cũng sẽ chạy song song, phải không?
kéo co

10
@tugberk, các cuộc gọi cơ sở dữ liệu là các hoạt động I / O, nhưng tất cả sẽ phụ thuộc vào cách bạn thực hiện chúng. Nếu bạn sử dụng một cuộc gọi cơ sở dữ liệu chặn như SqlCommand.ExecuteReaderbạn đang lãng phí mọi thứ vì đây là cuộc gọi chặn. Bạn đang chặn luồng mà cuộc gọi này thực hiện và nếu luồng này xảy ra là một luồng từ nhóm thì nó rất tệ. Bạn sẽ chỉ được hưởng lợi nếu bạn sử dụng Cổng hoàn thành I / O : SqlCommand.BeginExecuteReader. Nếu bạn không sử dụng IOCP bất kể bạn làm gì, đừng sử dụng bộ điều khiển không đồng bộ vì bạn sẽ gây ra nhiều thiệt hại hơn là có lợi cho hiệu suất chung của ứng dụng.
Darin Dimitrov

1
Chà, hầu hết thời gian tôi sử dụng mã EF đầu tiên. Tôi không chắc chắn nếu nó phù hợp. Tôi đưa ra một mẫu cho thấy những gì tôi thường làm. Tôi cập nhật câu hỏi, bạn có thể có một cái nhìn?
kéo co

3
@tugberk, bạn đang chạy chúng song song, vì vậy tổng thời gian thực hiện ít hơn so với nếu bạn chạy chúng tuần tự. Nhưng để chạy chúng, bạn sử dụng các luồng công nhân. Thật ra thì EF rất lười nên khi bạn _context.Foothực sự không thực hiện bất cứ điều gì. Bạn chỉ đang xây dựng một cây biểu hiện. Hãy cực kỳ cẩn thận với điều này. Việc thực hiện truy vấn chỉ được hoãn lại khi bạn bắt đầu liệt kê trên tập kết quả. Và nếu điều này xảy ra trong quan điểm thì điều này có thể là thảm họa đối với hiệu suất. Để háo hức thực hiện một truy vấn EF .ToList()ở cuối.
Darin Dimitrov

2
@tugberk, bạn sẽ cần một công cụ kiểm tra tải để mô phỏng nhiều người dùng song song trên trang web của bạn để xem cách nó hoạt động dưới tải nặng. Mini Profiler không thể mô phỏng tải trên trang web của bạn. Nó có thể giúp bạn xem và tối ưu hóa các truy vấn ADO.NET của bạn và lập hồ sơ một yêu cầu vô ích khi bạn cần xem trang web của bạn hoạt động như thế nào trong tình huống thực tế khi có nhiều người dùng đánh vào nó.
Darin Dimitrov

49

Có - tất cả các chủ đề đến từ nhóm chủ đề. Ứng dụng MVC của bạn đã đa luồng, khi một yêu cầu đến trong một luồng mới sẽ được lấy từ nhóm và được sử dụng để phục vụ yêu cầu. Chuỗi đó sẽ được 'khóa' (từ các yêu cầu khác) cho đến khi yêu cầu được phục vụ đầy đủ và hoàn thành. Nếu không có chủ đề có sẵn trong nhóm, yêu cầu sẽ phải đợi cho đến khi có sẵn.

Nếu bạn có bộ điều khiển không đồng bộ, họ vẫn nhận được một luồng từ nhóm nhưng trong khi thực hiện yêu cầu, họ có thể từ bỏ luồng, trong khi chờ điều gì đó xảy ra (và luồng đó có thể được cung cấp cho yêu cầu khác) và khi yêu cầu ban đầu cần một luồng một lần nữa nó nhận được một từ hồ bơi.

Sự khác biệt là nếu bạn có nhiều yêu cầu chạy dài (trong đó luồng đang chờ phản hồi từ thứ gì đó), bạn có thể hết luồng từ nhóm để phục vụ ngay cả các yêu cầu cơ bản. Nếu bạn có bộ điều khiển không đồng bộ, bạn không có thêm luồng nào ngoài những luồng đang chờ được trả lại cho nhóm và có thể phục vụ các yêu cầu khác.

Một ví dụ gần như ngoài đời thực ... Hãy nghĩ về nó giống như lên xe buýt, có năm người đang chờ để lên xe, người đầu tiên lên xe, trả tiền và ngồi xuống (tài xế phục vụ yêu cầu của họ), bạn lên xe (tài xế đang phục vụ yêu cầu của bạn) nhưng bạn không thể tìm thấy tiền của mình; khi bạn lục lọi trong túi, tài xế từ bỏ bạn và nhận hai người tiếp theo (phục vụ yêu cầu của họ), khi bạn tìm thấy tiền của mình, tài xế bắt đầu giao dịch với bạn một lần nữa (hoàn thành yêu cầu của bạn) - người thứ năm phải đợi cho đến khi bạn đã hoàn thành nhưng người thứ ba và thứ tư đã được phục vụ trong khi bạn mới đi được nửa đường. Điều này có nghĩa là người lái xe là chủ đề duy nhất và duy nhất từ ​​hồ bơi và hành khách là những yêu cầu. Quá phức tạp để viết nó sẽ hoạt động như thế nào nếu có hai trình điều khiển nhưng bạn có thể tưởng tượng ...

Nếu không có bộ điều khiển không đồng bộ, các hành khách phía sau bạn sẽ phải đợi tuổi trong khi bạn tìm kiếm tiền của mình, trong khi đó tài xế xe buýt sẽ không làm việc.

Vì vậy, kết luận là, nếu nhiều người không biết tiền của họ ở đâu (nghĩa là cần một thời gian dài để trả lời một cái gì đó mà tài xế đã yêu cầu) bộ điều khiển async có thể giúp thông qua các yêu cầu, tăng tốc quá trình từ một số người. Không có bộ điều khiển aysnc, mọi người sẽ đợi cho đến khi người đứng trước hoàn toàn bị xử lý. NHƯNG đừng quên rằng trong MVC bạn có rất nhiều trình điều khiển xe buýt trên một xe buýt nên async không phải là một lựa chọn tự động.


8
Tương tự rất tốt đẹp. Cảm ơn bạn.
Pittsburgh DBA

Tôi thích mô tả. Cảm ơn
Omer Cansizoglu

Cách tuyệt vời để giải thích nó. Cảm ơn bạn,
Anand Vyas

Câu trả lời của bạn kết hợp với câu trả lời của Darin tổng hợp toàn bộ cơ chế đằng sau bộ điều khiển không đồng bộ, nó là gì và quan trọng hơn là nó không phải là gì!
Nirman

Đây là một sự tương tự tốt đẹp, nhưng câu hỏi duy nhất của tôi là: anh chàng dò dẫm trong túi của anh ta trong sự tương tự của bạn sẽ là một loại công việc / xử lý trong ứng dụng của chúng tôi ... vậy quá trình nào hoạt động khi chúng tôi phát hành chuỗi? Chắc chắn đó là một chủ đề khác? Vậy chúng ta đạt được gì ở đây?
Tomuke

10

Có hai khái niệm chơi ở đây. Trước hết, chúng ta có thể làm cho mã của mình chạy song song để thực thi nhanh hơn hoặc lên lịch mã trên một luồng khác để tránh khiến người dùng phải chờ đợi. Ví dụ bạn đã có

public ViewResult Index() { 

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

    return View();
}

thuộc loại thứ hai. Người dùng sẽ nhận được phản hồi nhanh hơn nhưng tổng khối lượng công việc trên máy chủ cao hơn vì nó phải thực hiện cùng một công việc + xử lý luồng.

Một ví dụ khác về điều này sẽ là:

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

Bởi vì các yêu cầu chạy song song, người dùng sẽ không phải chờ miễn là chúng được thực hiện nối tiếp. Nhưng bạn nên nhận ra rằng chúng tôi sử dụng nhiều tài nguyên ở đây hơn là nếu chúng tôi chạy nối tiếp vì chúng tôi chạy mã ở nhiều luồng trong khi chúng tôi cũng đang chờ xử lý.

Điều này là hoàn toàn tốt trong một kịch bản khách hàng. Và nó khá phổ biến ở đó để bọc mã chạy dài đồng bộ trong một tác vụ mới (chạy nó trên một luồng khác) quá giữ cho ui đáp ứng hoặc song song để làm cho nó nhanh hơn. Một chủ đề vẫn được sử dụng cho toàn bộ thời gian mặc dù. Trên một máy chủ có tải cao, điều này có thể gây phản tác dụng vì bạn thực sự sử dụng nhiều tài nguyên hơn. Đây là những gì mọi người đã cảnh báo bạn về

Bộ điều khiển Async trong MVC có một mục tiêu khác mặc dù. Vấn đề ở đây là để tránh việc các chủ đề xoay quanh việc không làm gì cả (điều này có thể làm tổn thương khả năng mở rộng). Nó thực sự chỉ quan trọng nếu API bạn đang gọi có các phương thức không đồng bộ. Giống như WebClient.DowloadStringAsync ().

Vấn đề là bạn có thể để chủ đề của mình được trả về để xử lý các yêu cầu mới cho đến khi yêu cầu web kết thúc, nơi nó sẽ gọi bạn gọi lại, cùng một chủ đề mới và hoàn thành yêu cầu.

Tôi hy vọng bạn hiểu sự khác biệt giữa không đồng bộ và song song. Hãy nghĩ về mã song song là mã nơi chủ đề của bạn nằm xung quanh và chờ kết quả. Mặc dù mã không đồng bộ là mã nơi bạn sẽ được thông báo khi mã được hoàn thành và bạn có thể quay lại làm việc với nó, trong khi đó, luồng có thể thực hiện công việc khác.


6

Các ứng dụng có thể mở rộng quy mô tốt hơn nếu các hoạt động chạy không đồng bộ, nhưng chỉ khi có sẵn các tài nguyên để phục vụ các hoạt động bổ sung .

Các hoạt động không đồng bộ đảm bảo rằng bạn không bao giờ chặn một hành động vì đang thực hiện một hành động. ASP.NET có một mô hình không đồng bộ cho phép nhiều yêu cầu thực hiện song song. Có thể xếp hàng các yêu cầu lên và xử lý chúng theo yêu cầu, nhưng điều này sẽ không mở rộng khi bạn có hàng trăm yêu cầu được xếp hàng và mỗi yêu cầu mất 100ms để xử lý.

Nếu bạn có lưu lượng lớn, bạn thể không thực hiện các truy vấn của mình một cách không đồng bộ, vì có thể không có tài nguyên bổ sung để phục vụ các yêu cầu . Nếu không có tài nguyên dự phòng, các yêu cầu của bạn buộc phải xếp hàng, mất nhiều thời gian hơn hoặc hoàn toàn không thành công, trong trường hợp đó, chi phí không đồng bộ (mutexes và hoạt động chuyển ngữ cảnh) không cung cấp cho bạn bất cứ điều gì.

Theo như ASP.NET, bạn không có lựa chọn nào - nó sử dụng mô hình không đồng bộ, bởi vì đó là điều có ý nghĩa đối với mô hình máy chủ-máy khách. Nếu bạn đang viết mã riêng của mình bên trong sử dụng mẫu không đồng bộ để cố gắng mở rộng quy mô tốt hơn, trừ khi bạn đang cố gắng quản lý tài nguyên được chia sẻ giữa tất cả các yêu cầu, bạn sẽ không thực sự thấy bất kỳ cải tiến nào vì chúng đã được bọc trong một quy trình không đồng bộ mà không chặn bất cứ thứ gì khác.

Cuối cùng, tất cả đều chủ quan cho đến khi bạn thực sự nhìn vào những gì gây ra một nút cổ chai trong hệ thống của bạn. Đôi khi, rõ ràng là một mẫu không đồng bộ sẽ giúp (bằng cách ngăn chặn chặn tài nguyên được xếp hàng). Cuối cùng, chỉ đo lường và phân tích một hệ thống có thể chỉ ra nơi bạn có thể đạt được hiệu quả.

Biên tập:

Trong ví dụ của bạn, Task.Factory.StartNewcuộc gọi sẽ xếp hàng một thao tác trên nhóm luồng .NET. Bản chất của các luồng chủ đề là được sử dụng lại (để tránh chi phí tạo / hủy nhiều luồng). Khi hoạt động hoàn tất, luồng được giải phóng trở lại nhóm để được sử dụng lại bởi một yêu cầu khác (Trình thu gom rác không thực sự tham gia trừ khi bạn tạo một số đối tượng trong hoạt động của mình, trong trường hợp đó chúng được thu thập như bình thường phạm vi).

Theo như ASP.NET có liên quan, không có hoạt động đặc biệt ở đây. Yêu cầu ASP.NET hoàn thành mà không liên quan đến nhiệm vụ không đồng bộ. Mối quan tâm duy nhất có thể là nếu nhóm luồng của bạn đã bão hòa (nghĩa là không có luồng nào để phục vụ yêu cầu ngay bây giờ và cài đặt của nhóm không cho phép tạo nhiều luồng hơn), trong trường hợp đó, yêu cầu bị chặn chờ bắt đầu nhiệm vụ cho đến khi một chủ đề hồ bơi có sẵn.


Cảm ơn! Sau khi tôi đọc câu trả lời của bạn, tôi chỉnh sửa câu hỏi bằng một mẫu mã. Bạn có thể có một cái nhìn?
kéo co

Bạn có một câu thần kỳ ở đó cho tôi: Task.Factory.StartNewcuộc gọi sẽ xếp hàng một hoạt động trên nhóm luồng .NET. . Trong ngữ cảnh này, cái nào đúng ở đây: 1-) Nó tạo ra một luồng mới và khi nó được thực hiện, luồng đó quay trở lại threadpool và đợi ở đó để được sử dụng lại. 2-) Nó nhận được một luồng từ luồng và luồng đó quay trở lại luồng và chờ ở đó để được sử dụng lại. 3-) Nó có cách tiếp cận hiệu quả nhất và có thể làm một trong số chúng.
kéo co

1
Nhóm luồng tạo ra các luồng khi chúng được yêu cầu và tái chế các luồng khi chúng không được sử dụng. Hành vi chính xác của nó đã thay đổi trên các phiên bản CLR. Bạn có thể tìm thấy thông tin cụ thể về nó ở đây msdn.microsoft.com/en-us/l Library / 0a9477y.aspx
Paul Turner

Nó bắt đầu hình thành trong tâm trí tôi bây giờ. Vì vậy, CLR sở hữu nhóm luồng, phải không? Ví dụ, một ứng dụng WPF cũng có một khái niệm về nhóm luồng và nó cũng được xử lý với nhóm đó.
kéo co

1
Nhóm chủ đề là thứ riêng của nó trong CLR. Các thành phần khác "nhận biết" về nhóm này cho biết rằng họ sử dụng các luồng của Thread Pool (khi thích hợp) thay vì tạo và hủy các nhóm của riêng họ. Tạo hoặc hủy một luồng là một hoạt động tương đối tốn kém, vì vậy việc sử dụng nhóm là một hiệu quả lớn đối với các hoạt động ngắn hạn.
Paul Turner

2

Vâng, họ sử dụng một chủ đề từ nhóm chủ đề. Thực sự có một hướng dẫn khá xuất sắc từ MSDN sẽ giải quyết tất cả các câu hỏi của bạn và hơn thế nữa. Tôi đã tìm thấy nó khá hữu ích trong quá khứ. Kiểm tra nó ra!

http://msdn.microsoft.com/en-us/l Library / e7728598.aspx

Trong khi đó, các ý kiến ​​+ đề xuất mà bạn nghe về mã không đồng bộ nên được thực hiện bằng một hạt muối. Đối với người mới bắt đầu, chỉ cần làm một cái gì đó không đồng bộ không nhất thiết làm cho nó có quy mô tốt hơn và trong một số trường hợp có thể làm cho quy mô ứng dụng của bạn trở nên tồi tệ hơn. Các bình luận khác mà bạn đã đăng về "một lưu lượng lớn ..." cũng chỉ đúng trong một số bối cảnh nhất định. Nó thực sự phụ thuộc vào những gì hoạt động của bạn đang làm và cách chúng tương tác với các bộ phận khác của hệ thống.

Nói tóm lại, rất nhiều người có rất nhiều ý kiến ​​về async, nhưng họ có thể không chính xác trong bối cảnh. Tôi muốn tập trung vào các vấn đề chính xác của bạn và thực hiện kiểm tra hiệu suất cơ bản để xem bộ điều khiển async nào, v.v. thực sự xử lý với ứng dụng của bạn .


Tôi đã đọc tài liệu đó có thể hàng trăm lần và tôi vẫn còn rất nhiều nhầm lẫn (có thể vấn đề là tôi, người biết). Khi bạn nhìn xung quanh, bạn sẽ thấy rất nhiều ý kiến ​​không nhất quán về sự không đồng bộ trên ASP.NET MVC như bạn có thể thấy trong câu hỏi của tôi.
kéo co

cho câu cuối cùng: bên trong một hành động của bộ điều khiển, tôi đã truy vấn cơ sở dữ liệu 5 lần riêng biệt (tôi phải làm) và tất cả chỉ mất khoảng 400 ms. Sau đó, tôi đã triển khai AsyncControll và chạy chúng song song. Thời gian đáp ứng giảm đáng kể đến khoảng. 200 ms. Nhưng tôi không biết nó tạo ra bao nhiêu luồng, điều gì xảy ra với các luồng đó sau khi tôi hoàn thành chúng, sẽ GCđến và làm sạch chúng ngay sau khi tôi hoàn thành để ứng dụng của tôi không bị rò rỉ bộ nhớ, v.v. Bất kỳ ý tưởng về phần đó.
kéo co

đính kèm một trình sửa lỗi và tìm hiểu.
AR

0

Điều đầu tiên không phải là MVC mà là IIS, người duy trì nhóm luồng. Vì vậy, bất kỳ yêu cầu nào đến ứng dụng MVC hoặc ASP.NET đều được phục vụ từ các luồng được duy trì trong nhóm luồng. Chỉ khi tạo ứng dụng Asynch, anh ta gọi hành động này trong một luồng khác và giải phóng luồng ngay lập tức để có thể thực hiện các yêu cầu khác.

Tôi đã giải thích tương tự với một video chi tiết ( http://www.youtube.com/watch?v=wvg13n5V0V0/ "Bộ điều khiển MVC Asynch và bỏ đói chủ đề") cho thấy tình trạng chết đói của luồng xảy ra trong MVC và cách giảm thiểu của nó bằng cách sử dụng MVC Bộ điều khiển Asynch. Tôi cũng đã đo hàng đợi yêu cầu bằng perfmon để bạn có thể thấy cách giảm hàng đợi yêu cầu đối với MVC asynch và mức độ tệ nhất của nó đối với các hoạt động của Synch.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.