Câu hỏi Tại sao sẽ cố gắng / cuối cùng chứ không phải là một tuyên bố "sử dụng" giúp tránh một điều kiện chủng tộc?


Câu hỏi này liên quan đến nhận xét trong bài đăng khác tại đây: Hủy truy vấn khung thực thể

Tôi sẽ tái tạo ví dụ mã từ đó để làm rõ:

    var thread = new Thread((param) =>
    {
        var currentString = param as string;

        if (currentString == null)
        {
            // TODO OMG exception
            throw new Exception();
        }

        AdventureWorks2008R2Entities entities = null;
        try // Don't use using because it can cause race condition
        {
            entities = new AdventureWorks2008R2Entities();

            ObjectQuery<Person> query = entities.People
                .Include("Password")
                .Include("PersonPhone")
                .Include("EmailAddress")
                .Include("BusinessEntity")
                .Include("BusinessEntityContact");
            // Improves performance of readonly query where
            // objects do not have to be tracked by context
            // Edit: But it doesn't work for this query because of includes
            // query.MergeOption = MergeOption.NoTracking;

            foreach (var record in query 
                .Where(p => p.LastName.StartsWith(currentString)))
            {
                // TODO fill some buffer and invoke UI update
            }
        }
        finally
        {
            if (entities != null)
            {
                entities.Dispose();
            }
        }
    });

thread.Start("P");
// Just for test
Thread.Sleep(500);
thread.Abort();

Tôi không thể hiểu được ý kiến ​​nói rằng

Không sử dụng vì nó có thể gây ra tình trạng đua

entities là một biến cục bộ và sẽ không được chia sẻ nếu mã được nhập lại trên một chuỗi khác, và trong cùng một chuỗi nó có vẻ hoàn toàn an toàn (và thực sự tương đương với mã đã cho) để gán nó bên trong câu lệnh "using" theo cách thông thường, thay vì làm những việc thủ công với thử / cuối cùng. Ai có thể khai sáng cho tôi không?


23
2018-02-12 10:38


gốc


Bạn nên hỏi tác giả gốc. Nó làm cho không có ý nghĩa với tôi. - Jon Skeet
Tại sao bạn không hỏi người tạo mã đó? (FTFY) - sloth
Đó là suy nghĩ đầu tiên của tôi, nhưng tôi không thể tìm cách liên lạc với anh ấy, không có email trên blog của anh ấy và không có cách nào để gửi tin nhắn cá nhân trên diễn đàn này hoặc trên diễn đàn MSDN mà tôi có thể hiểu được (nhưng hãy chỉ cho tôi đúng hướng nếu tôi bị mờ và bỏ lỡ!) - Mike Nunan


Các câu trả lời:


Vâng, có một cuộc đua có thể có trong sử dụng tuyên bố. Trình biên dịch C # biến đổi

using (var obj = new Foo()) {
    // statements
}

đến:

var obj = new Foo();
try {
   // statements
}
finally {
   if (obj != null) obj.Dispose();
}

Cuộc đua xảy ra khi luồng bị hủy ngay giữa obj câu lệnh gán và khối thử. Tỷ lệ cược cực kỳ nhỏ nhưng không phải bằng không. Các đối tượng sẽ không được xử lý khi điều đó xảy ra. Lưu ý cách anh ấy viết lại mã đó bằng cách di chuyển nhiệm vụ bên trong khối thử để cuộc đua này không thể xảy ra. Không có gì thực sự sai về cơ bản khi cuộc đua xảy ra, việc vứt bỏ đồ vật không phải là một yêu cầu.

Phải lựa chọn giữa việc hủy bỏ luồng chỉ hiệu quả hơn và viết sử dụng báo cáo bằng tay, trước tiên bạn nên chọn không nhận được thói quen sử dụng Thread.Abort (). Tôi không thể khuyên bạn nên thực sự làm điều này, sử dụng tuyên bố có các biện pháp an toàn bổ sung để đảm bảo tai nạn không xảy ra, nó đảm bảo rằng đối tượng ban đầu được xử lý ngay cả khi đối tượng được gán lại bên trong câu lệnh sử dụng. Thêm mệnh đề đánh bắt cũng ít bị tai nạn hơn. Các sử dụng tuyên bố tồn tại cho giảm khả năng xảy ra lỗi, luôn sử dụng nó.


Noodling về một chút về vấn đề này, câu trả lời là phổ biến, có một tuyên bố C # phổ biến mà bị từ cùng một cuộc đua chính xác. Nó trông như thế này:

lock (obj) {
    // statements
}

Đã dịch sang:

Monitor.Enter(obj);
// <=== Eeeek!
try {
    // statements
}
finally {
    Monitor.Exit(obj);
}

Chính xác cùng một kịch bản, hủy bỏ thread có thể tấn công sau khi gọi Enter () và trước khi vào khối try. Điều này ngăn cản việc thực hiện cuộc gọi Exit (). Đây là đường Nastier hơn là một Dispose () gọi là không được thực hiện tất nhiên, điều này gần như chắc chắn sẽ gây ra bế tắc. Vấn đề là cụ thể với jitter x64, các chi tiết sordid được mô tả tốt trong này Bài đăng trên blog của Joe Duffy.

Rất khó khắc phục sự cố này một cách đáng tin cậy, việc di chuyển lệnh gọi Enter () bên trong khối thử không thể giải quyết được vấn đề. Bạn không thể chắc chắn rằng cuộc gọi Enter đã được thực hiện để bạn không thể tin cậy gọi phương thức Exit () mà không thể kích hoạt một ngoại lệ. Phương thức Monitor.ReliableEnter () mà Duffy đang nói đến cuối cùng cũng đã xảy ra. Phiên bản .NET 4 của Màn hình có quá tải TryEnter () ref bool lockTaken tranh luận. Bây giờ bạn biết nó là okay để gọi Exit ().

Vâng, những thứ đáng sợ mà đi BUMP trong đêm khi bạn không tìm kiếm. Viết mã an toàn có thể ngắt được cứng. Bạn sẽ được khôn ngoan để không bao giờ giả định rằng mã mà bạn không viết có tất cả những điều này được chăm sóc. Kiểm tra mã như vậy là vô cùng khó khăn vì cuộc đua rất hiếm. Bạn không bao giờ có thể chắc chắn.


41
2018-02-12 11:34



Cảm ơn Hans (và cảm ơn tất cả những người khác vì câu trả lời của bạn). Vì vậy, đó là một trường hợp không bệnh lý nhưng thực sự đủ điều kiện như là một điều kiện chủng tộc nếu một là chính xác về nó. Re sử dụng Thread.Abort (), tôi hoàn toàn đồng ý rằng nó là tốt nhất tránh nhưng trong trường hợp này không có tùy chọn khác thực sự, Entity Framework 4.x không hỗ trợ các truy vấn gián đoạn và tôi có cùng một vấn đề như OP của câu hỏi khác vì tôi đang tắt các truy vấn để phản hồi các hành động của người dùng cần phải bị gián đoạn sớm nếu người dùng thực hiện điều gì đó khác. - Mike Nunan
Tôi sẽ phải trì hoãn sự khôn ngoan thông thường về việc sử dụng EF. Nhưng có một lá cờ đỏ khổng lồ trong bình luận của bạn. "Không hỗ trợ các truy vấn gián đoạn" chỉ là một cách hay để nói "không hỗ trợ hủy bỏ chuỗi". Nếu EF thực sự đã hỗ trợ ngắt luồng thì việc thêm một cách để ngắt truy vấn sẽ không có vấn đề gì. Và sẽ được thêm vào api, nó là một tính năng rõ ràng mong muốn. Bạn chắc chắn đang chơi với lửa. - Hans Passant
Có, họ đã thêm nó vào EF 6.x, nhưng đó là trong phiên bản beta và không có hỗ trợ cho thư viện khách hàng Oracle mà chúng tôi đang sử dụng, vì vậy tôi đang bị mắc kẹt với hack này cho bây giờ. Tôi đã không chính xác khi tôi nói "làm gián đoạn truy vấn" - điều tôi thực sự muốn nói là không có sự hủy bỏ kiểu async / nhiệm vụ thích hợp để bạn bị mắc kẹt với Thread.Abort () nếu bạn không sẵn sàng để các truy vấn chạy đến khi hoàn thành ngay cả khi kết quả đã trở nên không liên quan. - Mike Nunan
"Không có gì thực sự sai về cơ bản khi cuộc đua xảy ra, việc vứt bỏ đồ vật không phải là một yêu cầu." Một lần tôi sẽ không đồng ý với tuyên bố này là nếu các nhà xây dựng đã được phân bổ hoặc mở một số tài nguyên bên ngoài, giống như một kết nối cơ sở dữ liệu gộp hoặc một số như vậy. Trong trường hợp đó, sẽ có một ít kết nối trong nhóm có sẵn cho các chủ đề khác. Làm điều đó đủ thời gian và bạn sẽ không còn có thể có được một kết nối với cơ sở dữ liệu. Nói chung, tuy nhiên, tôi sẽ không lo lắng về nó. - Tony Vitabile


Rất lạ, nguyên nhân using chỉ là đường cú pháp để thử - cuối cùng là khối.

Từ MSDN:

Bạn có thể đạt được kết quả tương tự bằng cách đặt đối tượng bên trong một thử   chặn và sau đó gọi Vứt bỏ trong một khối cuối cùng; trên thực tế, đây là   cách trình biên dịch sử dụng được dịch bởi trình biên dịch.


7
2018-02-12 10:50





Tùy thuộc vào việc bạn có sử dụng hay không using hoặc rõ ràng try/finally bạn có thể có mã hơi khác với mã mẫu bạn sẽ có

    AdventureWorks2008R2Entities entities = null;
    try // Don't use using because it can cause race condition
    {
        entities = new AdventureWorks2008R2Entities();
        ...
    } finally {
    } 

thay thế bằng câu lệnh bằng cách sử dụng nó có thể trông giống như

   using(var entities = new AdventureWorks2008R2Entities()){
      ...
   }

mà accordig làm §8.13 của đặc điểm kỹ thuật sẽ được mở rộng đến

    AdventureWorks2008R2Entities entities = new AdventureWorks2008R2Entities();
    try
    {
        ...
    } finally {
    } 

Chỉ có sự khác biệt thực sự là nhiệm vụ không nằm trong khối try / finally nhưng điều đó không có hậu quả về điều kiện chủng tộc nào có thể xảy ra (ngoài việc hủy bỏ chuỗi giữa phép gán và khối try như Hans cũng ghi chú)


4
2018-02-12 11:50





Nhận xét đó không có ý nghĩa gì khi sử dụng câu lệnh sẽ được dịch sang trình biên dịch thử / cuối cùng. Vì 'các thực thể' sẽ không được sử dụng bên ngoài phạm vi, nên sử dụng một statment dễ dàng hơn vì điều này sẽ tự động xử lý các tài nguyên.

Bạn có thể đọc thêm về điều này trên MSDN: sử dụng Báo cáo (Tham chiếu C #).


2
2018-02-12 10:58