Chờ đợi vs Nhiệm vụ.Wait - Bế tắc?


194

Tôi không hiểu sự khác biệt giữa Task.Waitawait.

Tôi có một cái gì đó tương tự như các chức năng sau trong dịch vụ ASP.NET WebAPI:

public class TestController : ApiController
{
    public static async Task<string> Foo()
    {
        await Task.Delay(1).ConfigureAwait(false);
        return "";
    }

    public async static Task<string> Bar()
    {
        return await Foo();
    }

    public async static Task<string> Ros()
    {
        return await Bar();
    }

    // GET api/test
    public IEnumerable<string> Get()
    {
        Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());

        return new string[] { "value1", "value2" }; // This will never execute
    }
}

Nơi nào Getsẽ bế tắc.

Điều gì có thể gây ra điều này? Tại sao điều này không gây ra vấn đề khi tôi sử dụng chờ đợi chứ không phải là await Task.Delay?


@Servy: Tôi sẽ quay lại với repo ngay khi có thời gian. Bây giờ nó hoạt động với Task.Delay(1).Wait()nó là đủ tốt.
ronag

2
Task.Delay(1).Wait()về cơ bản là điều tương tự như Thread.Sleep(1000). Trong mã sản xuất thực tế, nó hiếm khi thích hợp.
Phục vụ

@ronag: Bạn WaitAllđang gây ra bế tắc. Xem liên kết đến blog của tôi trong câu trả lời của tôi để biết thêm chi tiết. Bạn nên sử dụng await Task.WhenAllthay thế.
Stephen Cleary

6
@ronag Bởi vì bạn có ConfigureAwait(false)một đơn cuộc gọi đến Barhoặc Rossẽ không bế tắc, nhưng vì bạn có một đếm được rằng đang tạo ra nhiều hơn một và sau đó chờ đợi trên tất cả các, thanh đầu tiên sẽ bế tắc thứ hai. Nếu bạn await Task.WhenAllthay vì chờ đợi trên tất cả các nhiệm vụ, để bạn không chặn bối cảnh ASP, bạn sẽ thấy phương thức trở lại bình thường.
Phục vụ

2
@ronag tùy chọn khác của bạn sẽ được thêm .ConfigureAwait(false) tất cả các con đường lên cây cho đến khi bạn chặn, như vậy không có gì là bao giờ cố gắng để lấy lại với bối cảnh chính; rằng sẽ làm việc. Một lựa chọn khác là quay lên một bối cảnh đồng bộ hóa bên trong. Liên kết . Nếu bạn đặt nó vào, nó sẽ chặn tất cả mọi thứ Task.WhenAllmột AsyncPump.Runcách hiệu quả mà không cần bạn phải ở ConfigureAwaitbất cứ đâu, nhưng đó có lẽ là một giải pháp quá phức tạp.
Phục vụ

Câu trả lời:


268

Waitawait- trong khi về mặt khái niệm tương tự - thực sự hoàn toàn khác nhau.

Waitsẽ đồng bộ chặn cho đến khi nhiệm vụ hoàn thành. Vì vậy, chủ đề hiện tại bị chặn theo nghĩa đen đang chờ nhiệm vụ hoàn thành. Theo nguyên tắc chung, bạn nên sử dụng " asynctất cả đường xuống"; đó là, đừng chặn asyncmã. Trên blog của mình, tôi đi vào chi tiết về cách chặn trong mã không đồng bộ gây ra bế tắc .

awaitsẽ chờ đợi không đồng bộ cho đến khi nhiệm vụ hoàn thành. Điều này có nghĩa là phương thức hiện tại bị "tạm dừng" (trạng thái của nó bị bắt) và phương thức trả về một tác vụ không hoàn chỉnh cho người gọi của nó. Sau đó, khi awaitbiểu thức hoàn thành, phần còn lại của phương thức được lên lịch như một phần tiếp theo.

Bạn cũng đã đề cập đến một "khối hợp tác", theo đó tôi giả sử bạn có nghĩa là một nhiệm vụ mà bạn đang Waitthực hiện có thể thực hiện trên chuỗi chờ. Có những tình huống điều này có thể xảy ra, nhưng đó là một sự tối ưu hóa. Có nhiều tình huống không thể xảy ra, như nếu tác vụ dành cho người lập lịch khác hoặc nếu nó đã bắt đầu hoặc nếu đó là tác vụ không phải mã (ví dụ như trong ví dụ mã của bạn: Waitkhông thể thực thi Delaytác vụ nội tuyến vì không có mã cho nó).

Bạn có thể tìm thấy async/ awaitgiới thiệu của tôi hữu ích.


4
Tôi nghĩ rằng có một sự hiểu lầm, Waitlàm việc awaitbế tắc tốt .
ronag

5
Không, bộ lập lịch nhiệm vụ sẽ không làm điều đó. Waitchặn chuỗi, và nó không thể được sử dụng cho những thứ khác.
Stephen Cleary

8
@ronag Tôi đoán là bạn vừa trộn lẫn tên phương thức của mình và bế tắc của bạn thực sự gây ra với mã chặn và làm việc với awaitmã. Hoặc là, hoặc bế tắc không liên quan đến một trong hai và bạn đã chẩn đoán sai vấn đề.
Phục vụ

3
@hexterminator: Đây là do thiết kế - nó hoạt động rất tốt cho các ứng dụng UI, nhưng có xu hướng cản trở các ứng dụng ASP.NET. ASP.NET Core đã khắc phục điều này bằng cách loại bỏ SynchronizationContext, do đó, việc chặn trong yêu cầu ASP.NET Core không còn bế tắc nữa.
Stephen Cleary

1
trên msdn ở đây, nó nói rằng chờ đợi chạy trên luồng riêng biệt không đồng bộ. Tui bỏ lỡ điều gì vậy? msdn.microsoft.com/en-us/l
Library / hh195051 (v = vs.110) .aspx

6

Dựa trên những gì tôi đọc được từ các nguồn khác nhau:

An await biểu thức không chặn luồng mà nó đang thực thi. Thay vào đó, nó làm cho trình biên dịch đăng ký phần còn lại của asyncphương thức dưới dạng tiếp tục trên tác vụ được chờ đợi. Kiểm soát sau đó trả về cho người gọi asyncphương thức. Khi tác vụ hoàn thành, nó gọi tiếp tục và thực thi asyncphương thức tiếp tục ở nơi nó dừng lại.

Để đợi một đơn taskhoàn thành, bạn có thể gọi Task.Waitphương thức của nó . Một cuộc gọi đến Waitphương thức chặn luồng cuộc gọi cho đến khi cá thể lớp duy nhất đã hoàn thành thực thi. Wait()Phương thức không tham số được sử dụng để chờ vô điều kiện cho đến khi một tác vụ hoàn thành. Tác vụ mô phỏng công việc bằng cách gọiThread.Sleep phương thức để ngủ trong hai giây.

Bài viết này cũng là một đọc tốt.


3
"Điều đó không đúng về mặt kỹ thuật sao? Ai đó có thể vui lòng làm rõ không?" - tôi có thể làm rõ; bạn đang hỏi đó như một câu hỏi? (Tôi chỉ muốn rõ ràng cho dù bạn đang hỏi và trả lời). Nếu bạn đang hỏi: nó có thể hoạt động tốt hơn như một câu hỏi riêng biệt; không có khả năng thu thập các phản hồi mới ở đây như một câu trả lời
Marc Gravell

1
Tôi đã trả lời câu hỏi và hỏi một câu hỏi riêng cho sự nghi ngờ tôi có ở đây stackoverflow.com/questions/53654006/ cảm ơn @MarcGravell. Bạn có thể vui lòng xóa bỏ phiếu bầu của bạn cho câu trả lời ngay bây giờ?
Ayushmati

"Bạn có thể vui lòng xóa bỏ phiếu bầu của bạn cho câu trả lời ngay bây giờ không?" - đó không phải là của tôi; nhờ có ♦, bất kỳ phiếu bầu nào của tôi sẽ có hiệu lực ngay lập tức. Tuy nhiên, tôi không nghĩ rằng điều này trả lời các điểm chính của câu hỏi, đó là về hành vi bế tắc.
Marc Gravell

-2

Một số sự kiện quan trọng đã không được đưa ra trong các câu trả lời khác:

"Async await" phức tạp hơn ở cấp độ CIL và do đó tốn thời gian bộ nhớ và CPU.

Bất kỳ nhiệm vụ có thể bị hủy nếu thời gian chờ đợi là không thể chấp nhận.

Trong trường hợp "async await", chúng tôi không có xử lý cho một nhiệm vụ như vậy để hủy bỏ nó hoặc giám sát nó.

Sử dụng Nhiệm vụ linh hoạt hơn thì "async await".

Bất kỳ chức năng đồng bộ hóa có thể được bao bọc bởi async.

public async Task<ActionResult> DoAsync(long id) 
{ 
    return await Task.Run(() => { return DoSync(id); } ); 
} 

Tôi không hiểu lý do tại sao tôi phải sống với phương thức đồng bộ hóa và không đồng bộ mã hoặc sử dụng hack.

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.