2012-06-22 20 views
8

Một thực tế phổ biến để tránh điều kiện chủng tộc (trong các ứng dụng đa luồng) khi kích hoạt các sự kiện là:Chủ đề an toàn cho sự kiện kêu gọi

EventHandler<EventArgs> temp = SomeEvent; 
if (temp != null) temp(e); 

"Remember that delegates are immutable and this is why this technique works in theory. However, what a lot of developers don't realize is that this code could be optimized by the compiler to remove the local temp variable entirely. If this happens, this version of the code is identical to the first version, so a NullReferenceException is still possible." 

Vấn đề (theo sách) là "mã này có thể được tối ưu hóa Nếu điều này xảy ra, phiên bản này của mã giống hệt với phiên bản đầu tiên, do đó, một NullReferenceException vẫn có thể "

Theo CLR qua C#, đây là cách tốt hơn để buộc trình biên dịch sao chép con trỏ sự kiện.

virtual void OnNewMail(NewMailEventArgs e) 
{ 
    EventHandler<NewMailEventArgs> temp = 
          Interlocked.CompareExchange(ref NewMail, null, null); 
    if (temp != null) 
     temp(this, e); 
} 

Ở đây, CompareExchange thay đổi tham chiếu NewMail để null nếu nó là null và không làm thay đổi NewMail nếu nó không phải là null. Nói cách khác, CompareExchange không thay đổi giá trị trong NewMail, nhưng nó trả về giá trị bên trong NewMail theo cách nguyên tử, an toàn. Richter, Jeffrey (2010-02-12). CLR qua C# (tr. 265). OReilly Media - A. Kindle Edition.

Tôi đang ở trên .Net 4.0 framework và không chắc chắn điều này có thể hoạt động như thế nào, vì Interlocked.CompareExchange dự kiến ​​tham chiếu đến một vị trí chứ không phải tham chiếu đến sự kiện.

Hoặc có lỗi trong sách hoặc tôi đã giải thích sai nó. Có ai đã triển khai phương pháp này không? Hoặc có cách nào tốt hơn để ngăn chặn điều kiện chủng tộc ở đây?

CẬP NHẬT

đó là lỗi của tôi, mã lặp lại hoạt động. tôi đã có sai quy định đúc, nhưng theo Bradley (dưới đây) nó không phải là cần thiết trong. Net 2.0 và lên trên cửa sổ.

+0

http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx có thể là thú – Vlad

Trả lời

7

Trình biên dịch (hoặc JIT) không được phép tối ưu hóa if/temp đó (trong CLR 2.0 trở lên); các CLR 2.0 Memory Model không cho phép đọc từ heap được giới thiệu (quy tắC# 2).

Vì vậy, MyEvent không thể đọc lần thứ hai; giá trị của temp phải được đọc trong câu lệnh if.

Xem my blog post để có cuộc thảo luận mở rộng về tình huống này và giải thích lý do mẫu chuẩn là tốt. Tuy nhiên, nếu bạn đang chạy trên một CLR không phải của Microsoft (ví dụ, mono) mà không cung cấp mô hình bộ nhớ CLR 2.0 đảm bảo (nhưng chỉ theo mô hình bộ nhớ ECMA), hoặc bạn đang chạy trên Itanium (ví dụ:). trong đó có một mô hình bộ nhớ phần cứng khét tiếng yếu), bạn sẽ cần mã như Richter's để loại bỏ một điều kiện chủng tộc tiềm năng.

Liên quan đến câu hỏi của bạn về Interlocked.CompareExchange, cú pháp public event EventHandler<NewMailEventArgs> NewMail chỉ là C# cú pháp đường cho tuyên bố một lĩnh vực riêng của loại EventHandler<NewMailEventArgs> và một sự kiện nào đó có addremove phương pháp. Cuộc gọi Interlocked.CompareExchange đọc giá trị của trường riêng tư EventHandler<NewMailEventArgs>, vì vậy mã này biên dịch và hoạt động như mô tả Richter; nó chỉ là không cần thiết trong CLR của Microsoft.

+0

tôi đã cập nhật ví dụ của mình với các bình luận từ tác giả. nếu những gì bạn đang nói là đúng, nó trực tiếp mâu thuẫn với những gì Jeffrey Richter đang nói.Và tôi đã bắt gặp anh ta với những tuyên bố không chính xác trong cuốn sách này trước đây .. tôi chỉ muốn xác nhận cách chính xác để làm điều này .. –

+0

Chỉ trong các cửa sổ/.Net complier/JIT, điều này là không đúng với Mono. http://www.ecma-international.org/publications/standards/Ecma-335.htm – caesay

+0

@caesay: Điểm tốt; Tôi đã giải quyết điều đó trong bài đăng trên blog của mình, nhưng không phải trong câu trả lời; Tôi sẽ cập nhật. –

1

Tôi đoán bạn bỏ lỡ thông dịch. Vị trí có nghĩa là chỉ một con trỏ đến tham chiếu đối tượng [phiên bản msdn: Đối tượng đích được so sánh với so sánh và có thể được thay thế.]. Mã sau hoạt động tốt trong .NEt 4.0

public class publisher 
{ 

    public event EventHandler<EventArgs> TestEvent; 
    protected virtual void OnTestEvent(EventArgs e) 
    { 
     EventHandler<EventArgs> temp = Interlocked.CompareExchange(ref TestEvent, null, null); 
     if (temp != null) 
      temp(this,e); 
    } 
} 
3

Bây giờ đây chỉ là câu trả lời một phần cho câu hỏi của bạn, vì tôi không thể bình luận về việc sử dụng Interlocked.CompareExchange, tuy nhiên tôi nghĩ thông tin này có thể hữu ích.

Vấn đề là trình biên dịch có thể tối ưu hóa rằng nếu/temp đi,

Vâng, theo CLR thông qua C# (p. 264-265)

[Các ] code có thể được tối ưu hóa bởi trình biên dịch để loại bỏ biến cục bộ […] hoàn toàn. Nếu điều này xảy ra, phiên bản này của mã giống hệt với [phiên bản tham chiếu sự kiện hai lần], do đó, một NullReferenceException vẫn có thể thực hiện được.

Vì vậy, nó là thể, tuy nhiên, điều quan trọng để biết rằng Microsoft (JIT) biên dịch just-in-time không bao giờ tối ưu hóa đi các biến cục bộ. Mặc dù điều này có thể thay đổi, nhưng không chắc, bởi vì nó có thể sẽ phá vỡ rất nhiều ứng dụng.

Điều này là do Net có một mô hình bộ nhớ mạnh mẽ: http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S5

đọc và viết không thể giới thiệu.

Mô hình này không cho phép đọc được giới thiệu, tuy nhiên, vì điều này sẽ có nghĩa refetching một giá trị từ bộ nhớ, và trong bộ nhớ mã khóa thấp có thể được thay đổi.

 

Tuy nhiên, Mono, mà sau một much weaker memory model, có thể tối ưu hóa mà biến địa phương đi.

Điểm mấu chốt: Trừ khi bạn dự định sử dụng Mono, đừng lo lắng về điều đó.

Và thậm chí sau đó, hành vi đó có thể bị chặn bằng các khai báo dễ bay hơi.

+0

bạn đúng .. tôi đã nhảy đến một kết luận ở đó. đã điều chỉnh bài đăng của tôi và xóa câu lệnh "được tối ưu hóa". –

0

tôi bạn nhìn vào IL sản xuất bạn sẽ thấy rằng phương pháp này được gọi là như thế này

IL_000d: ldsflda class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs> ConsoleApplication1.Program::MyEvent 
IL_0012: ldnull 
IL_0013: ldnull 
IL_0014: call  !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs>>(!!0&,!!0,!!0) 

thấy rằng ldsflda - sự kiện của tôi là tĩnh nhưng nó nạp địa chỉ của một trường . Trường đó là trường đại biểu được tạo tự động mà trình biên dịch tạo ra cho mỗi sự kiện.

lĩnh vực này được định nghĩa như thế này:

.field private static class [mscorlib]System.EventHandler`1<class [mscorlib]System.EventArgs> MyEvent 
Các vấn đề liên quan