Tại sao các máy trạng thái không đồng bộ lại là các lớp (chứ không phải cấu trúc) trong Roslyn?


87

Hãy xem xét phương pháp không đồng bộ rất đơn giản này:

static async Task myMethodAsync() 
{
    await Task.Delay(500);
}

Khi tôi biên dịch điều này với VS2013 (trình biên dịch trước Roslyn), máy trạng thái được tạo là một cấu trúc.

private struct <myMethodAsync>d__0 : IAsyncStateMachine
{  
    ...
    void IAsyncStateMachine.MoveNext()
    {
        ...
    }
}

Khi tôi biên dịch nó với VS2015 (Roslyn), mã được tạo là:

private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
    ...
    void IAsyncStateMachine.MoveNext()
    {
        ...
    }
}

Như bạn có thể thấy Roslyn tạo một lớp (chứ không phải một cấu trúc). Nếu tôi nhớ không nhầm thì những triển khai đầu tiên của hỗ trợ async / await trong trình biên dịch cũ (tôi đoán là CTP2012) cũng đã tạo ra các lớp và sau đó nó được thay đổi thành cấu trúc vì lý do hiệu suất. (trong một số trường hợp, bạn hoàn toàn có thể tránh quyền anh và phân bổ đống…) (Xem phần này )

Có ai biết tại sao điều này lại được thay đổi ở Roslyn không? (Tôi không có bất kỳ vấn đề nào liên quan đến điều này, tôi biết rằng thay đổi này là minh bạch và không thay đổi hành vi của bất kỳ mã nào, tôi chỉ tò mò)

Biên tập:

Câu trả lời từ @Damien_The_Un Believer (và mã nguồn :)) imho giải thích mọi thứ. Hành vi được mô tả của Roslyn chỉ áp dụng cho bản dựng gỡ lỗi (và điều đó là cần thiết vì giới hạn CLR được đề cập trong nhận xét). Trong Release, nó cũng tạo ra một cấu trúc (với tất cả các lợi ích của điều đó ..). Vì vậy, đây có vẻ là một giải pháp rất thông minh để hỗ trợ cả Chỉnh sửa và Tiếp tục và hiệu suất tốt hơn trong sản xuất. Công cụ thú vị, cảm ơn cho tất cả những người đã tham gia!


2
Tôi nghi ngờ rằng họ quyết định độ phức tạp (cấu trúc có thể thay đổi lại) là không đáng. asynccác phương thức hầu như luôn có một điểm không đồng bộ thực sự - một điểm awaitmang lại quyền điều khiển, điều này sẽ yêu cầu cấu trúc phải được đóng hộp. Tôi tin rằng cấu trúc sẽ chỉ làm giảm áp lực bộ nhớ cho asynccác phương pháp đã tình cờ chạy đồng bộ.
Stephen Cleary

Câu trả lời:


112

Tôi không biết trước bất kỳ điều gì về điều này, nhưng vì Roslyn là mã nguồn mở ngày nay, chúng ta có thể tìm kiếm mã để giải thích.

Và đây, trên dòng 60 của AsyncRewriter , chúng tôi tìm thấy:

// The CLR doesn't support adding fields to structs, so in order to enable EnC in an async method we need to generate a class.
var typeKind = compilationState.Compilation.Options.EnableEditAndContinue ? TypeKind.Class : TypeKind.Struct;

Vì vậy, mặc dù có một số hấp dẫn đối với việc sử dụng structs, chiến thắng lớn của việc cho phép Chỉnh sửa và Tiếp tục hoạt động trong asynccác phương thức rõ ràng đã được chọn là phương án tốt hơn.


18
Bắt rất tốt! Và dựa trên điều này đây là những gì tôi cũng phát hiện ra: Điều này chỉ xảy ra khi bạn xây dựng nó trong debug (có lý, đó là khi bạn làm EnC ..), nhưng trong Release họ tạo ra một cấu trúc (rõ ràng là EnableEditAndContinue là sai trong trường hợp đó .. .). Btw. Tôi cũng đã thử nhìn vào mã, nhưng không tìm thấy điều này. Cảm ơn nhiều!
gregkalapos

3

Thật khó để đưa ra câu trả lời dứt khoát cho điều gì đó như thế này (trừ khi ai đó từ nhóm biên dịch ghé thăm :)), nhưng có một số điểm bạn có thể xem xét:

Hiệu suất "thưởng" của các cấu trúc luôn là một sự cân bằng. Về cơ bản, bạn nhận được những điều sau:

  • Ngữ nghĩa giá trị
  • Phân bổ ngăn xếp có thể có (thậm chí có thể đăng ký?)
  • Tránh chuyển hướng

Điều này có nghĩa là gì trong trường hợp chờ đợi? Thực ra thì ... không có gì. Chỉ có một khoảng thời gian rất ngắn trong đó máy trạng thái nằm trên ngăn xếp - hãy nhớ, awaitthực hiện một cách hiệu quả return, vì vậy ngăn xếp phương thức sẽ chết; máy trạng thái phải được bảo quản ở đâu đó, và "đâu đó" chắc chắn nằm trên heap. Thời gian tồn tại của ngăn xếp không phù hợp tốt với mã không đồng bộ :)

Ngoài ra, máy trạng thái còn vi phạm một số nguyên tắc tốt để xác định cấu trúc:

  • structs phải lớn nhất là 16 byte - máy trạng thái chứa hai con trỏ, tự chúng lấp đầy giới hạn 16 byte trên 64 bit. Ngoài ra, có chính trạng thái, vì vậy nó vượt quá "giới hạn". Đây không phải là vấn đề lớn , vì rất có thể nó chỉ được chuyển qua tham chiếu, nhưng lưu ý rằng điều đó không hoàn toàn phù hợp với trường hợp sử dụng cho struct - một struct về cơ bản là một loại tham chiếu.
  • structs nên là bất biến - tốt, điều này có lẽ không cần phải bình luận nhiều. Đó là một cỗ máy nhà nước . Một lần nữa, đây không phải là vấn đề lớn, vì cấu trúc là mã được tạo tự động và riêng tư, nhưng ...
  • structs nên đại diện một cách hợp lý cho một giá trị duy nhất. Chắc chắn không phải trường hợp ở đây, nhưng điều đó đã xảy ra sau khi có trạng thái có thể thay đổi ngay từ đầu.
  • Nó không nên được đóng hộp thường xuyên - không phải là một vấn đề ở đây, vì chúng tôi đang sử dụng generic ở khắp mọi nơi . Trạng thái cuối cùng ở đâu đó trên heap, nhưng ít nhất nó không được đóng hộp (tự động). Một lần nữa, thực tế là nó chỉ được sử dụng trong nội bộ khiến điều này trở nên vô nghĩa.

Và tất nhiên, tất cả điều này là trong trường hợp không có đóng cửa. Khi bạn có các địa phương (hoặc các trường) đi qua các awaits, trạng thái sẽ tăng cao hơn nữa, hạn chế tính hữu ích của việc sử dụng một cấu trúc.

Với tất cả những điều này, cách tiếp cận lớp chắc chắn sạch hơn và tôi sẽ không mong đợi bất kỳ sự gia tăng hiệu suất đáng chú ý nào từ việc sử dụng một structthay thế. Tất cả các đối tượng liên quan đều có thời gian tồn tại tương tự nhau, vì vậy cách duy nhất để cải thiện hiệu suất bộ nhớ là tạo tất cả chúng struct(ví dụ: lưu trữ trong một bộ đệm nào đó) - điều này là không thể trong trường hợp chung. Và hầu hết các trường hợp mà bạn sử dụng awaitngay từ đầu (tức là một số hoạt động I / O không đồng bộ) đã liên quan đến các lớp khác - ví dụ: bộ đệm dữ liệu, chuỗi ... Có khả năng bạn sẽ không muốn awaitthứ gì đó chỉ trả về 42mà không cần thực hiện phân bổ đống.

Cuối cùng, tôi muốn nói rằng nơi duy nhất mà bạn thực sự thấy sự khác biệt về hiệu suất thực sự sẽ là điểm chuẩn. Và tối ưu hóa cho các điểm chuẩn là một ý tưởng ngớ ngẩn, ít nhất là ...


Bạn không luôn luôn cần một thành viên của nhóm biên dịch khi bạn có thể đi và đọc các nguồn, và họ đã để lại một bình luận hữu ích :-)
Damien_The_Unbeliever

3
@Damien_The_Unbeliever Yeah, đó là chắc chắn một phát hiện tuyệt vời, tôi đã bỏ phiếu tán câu trả lời của bạn: P
Luaan

1
Cấu trúc giúp ích rất nhiều trong trường hợp mã không chạy không đồng bộ, ví dụ như dữ liệu đã có trong bộ đệm.
Ian Ringrose
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.