C# event線程安全

来源:http://www.cnblogs.com/chelxom/archive/2016/01/13/5128336.html
-Advertisement-
Play Games

突然想到有關C#中使用event特性時關於線程安全的問題,以前雖然有遵從“複製引用+null判斷”的模式(盲目地),但沒有深入瞭解和思考。為之查詢了資料和實驗,對此有了進一步的理解。一般event使用模式定義(field-like event):public event EventHandler D...


突然想到有關C#中使用event特性時關於線程安全的問題,以前雖然有遵從“複製引用+null判斷”的模式(盲目地),但沒有深入瞭解和思考。

為之查詢了資料和實驗,對此有了進一步的理解。

 

一般event使用模式

定義(field-like event):

public event EventHandler Done;

類內raise:

protected void OnDone()
{
    var done = Done;
    if (done != null)
    {
        done(this, new EventArgs());
    }
}

不禁要問,為何要複製引用?多線程下表現如何?

 

關於C#3.0和C#4.0中編譯器對event實現的整理

為瞭解決上面哪些疑惑,我查了一些資料,其中有來自當時C#編譯器開發組成員的一篇博文 Field-like Events Considered Harmful

這篇博文介紹了C#3.0中編譯器對於field-like event(也是最常見的使用方式)的實現。

 

對於如此的代碼,

class EventInCS3
{
    public event EventHandler Done;
}

編譯器會將其轉換成:

class EventInCS3
{
    private EventHandler __Done; // 1
    public event EventHandler Done
    {
        add
        {
            lock (this) // 2
            {
                __Done = __Done + value; // 3
            }
        }
        remove
        {
            lock (this) { __Done = __Done - value; }
        }
    }
}

有以下幾點值得註意(同註釋編號):

1.event下隱藏的真正delegate鏈。實際上我們使用的是子類MulticastDelegate(可以參考 開源的coreclr實現)。

3.正如+、-操作符對於string類型是起字元串組合作用,其對於delegate類型也同樣是起到兩條鏈的組合作用(參考 MSDN),實際上是調用了Delegate.CombineDelegate.Remove。同時也引入了經典的線程問題(修改丟失)。

2.為瞭解決多線程問題,使用了lock。

(就先不管這個lock(this)了。當然上面提到的 博文 里提到了,編譯器並不是通過lock,繼而通過Monitor的靜態方法來同步,而是通過IL即MethodImplAttribute(MethodImplOptions.Synchronized)實現。這些都是C#本身不推薦的方法。)

 

而在C#4.0中,同步的實現有了變化,同樣參見同一作者兩年後的 這一篇博文

編譯器預設的addremove實現,改為使用compare and swap來實現lock-free同步。值得註意的是,delegate是不可更改的類型,即+=、-=之後,會指向一個新的對象,而不再是原對象(類似string)。

通過IL查看程式集里生成的add_Doneremove_Done,可以發現端倪,大致會生成如下的代碼:

static void add_Done(EventHandler value)
{
    EventHandler V_0 = __Done;
    EventHandler V_1, V_2;
    do
    {
        V_1 = V_0;
        V_2 = (EventHandler)Delegate.Combine(V_1, value);
        V_0 = Interlocked.CompareExchange<EventHandler>(ref __Done, V_2, V_1);
    } while (V_0 != V_1);
}

 

C#4.0中event相關的語義變化整理

在同一作者的 另一篇博文 中,介紹了C#4.0中event相關的語義變化,主要是+=、-=操作符的語義變化

 

在C#3.0中,對於一個event,如果在該類之外訪問這個event,則會被認為是訪問這個event本身,如我們熟知的只能通過+=、-=這兩個操作符來訪問(即是調用對應的add、remove訪問器);而在類的內部,所有對這個event的訪問,都會被認為是訪問作為event實現的delegate本身(即訪問Done,實際上訪問到的是__Done)。

這麼處理的話,我們就能在OnDone方法里複製引用,判斷null,進行調用。因為此時Done這個標識符,代表的是一個EventHandler對象的引用。

C#3.0的問題也在於此,這種情況下,我們寫下

Done += SomeHandlerMethod;

時,+=實際是調用了:

EventHandler EventHandler.operator +(EventHandler left, EventHandler right)

在Visual Studio 2015里寫一個普通的、非event的EventHandler的+=運算,滑鼠放在+=上時,顯示的也是這個函數簽名。C#3.0時即使對event也是這麼處理的。

導致我們失去了預設add訪問器提供的同步功能

 

而這一現象在C#4.0中得到了改善。在類內部訪問event的標識符時,+=、-=操作符就會被認為是add、remove的調用了。

可知在C#4.0寫下同樣的代碼時,+=調用的簽名為:

void EventInCS4.Done.add 

 

自定義event訪問器

自定義event時(非field-like event),我們自己編寫的add、remove訪問器就沒有預設的同步了。如果要考慮線程安全,需要手動加上同步(比如lock(someLockObject))。

此時,在類內部訪問event標識符,只會被當成是訪問event本身。要引發事件(Done)的話,需訪問對應delegate(__Done(this, new EventArgs()))。

 

操作event的正確方式

一般情況下無需自己實現event,用field-like就好了。

因為不管是通過event標識符訪問delegate(field-like event),還是直接訪問delegate(自定義event),我們得到的都是delegate對象的引用,而且delegate對象是不可更改的。引用的複製是原子的。所以我們可以隨意地複製該delegate的引用,然後判斷null並invoke。

 

一些code snippet如:

  1. 通過擴展方法來引發事件:
    public static class EventExtension
    {
        public static void Raise<T>(this EventHandler<T> handler, object sender, T args)
        {
            if (handler != null)
            {
                handler(sender, args);
            }
        }
        public static void Raise(this EventHandler handler, object sender, EventArgs args); // 重載版
    }

    delegate的引用會以pass-by-value形式得到複製,所以直接

    Done.Raise(this, new EventArgs());
  2.  通過C#6.0提供的Null-conditional操作符
    Done?.Invoke(this, new EventArgs());

    null-conditional操作符也會進行引用的複製,所以是線程安全的。(沒有Done?(...)這種寫法)

 

對於編譯器是否會將複製引用作為重覆的局部變數優化掉,以至於在一些情況下需要使用諸如以下的方式的問題,我沒有深入瞭解。

Interlocked.CompareExchange(ref Done, null, null);

簡單查詢一下之後,得知對於微軟自家的CLR無需關心這個問題,蓋其遵循較嚴格的記憶體模型(memory model),不會引入新的讀取操作。但其他情況下有可能存在這樣的問題。相關文章和討論鏈接如下:

  1. http://stackoverflow.com/questions/11159176/thread-safe-event-calls
  2. http://code.logos.com/blog/2008/11/events_and_threads_part_4.html
  3. Understand the Impact of Low-Lock Techniques in Multithreaded Apps MSDN Magazine Oct 2005 (需要下載chm看)

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作者:[美]Adam Freeman 來源:《精通ASP.NET MVC 4》ASP.NET MVC 是微軟的一個 Web開發框架,它整合了“模型—視圖—控制器(MVC)”架構的高效與整潔、敏捷開發的最新的思想與技術以及當前ASP.NET 平臺的精華部分。ASP.NET MVC 可以完全替代傳統的....
  • 一. DescriptionAttribute的普通使用方式1.1 使用示例 DescriptionAttribute特性可以用到很多地方,比較常見的就是枚舉,通過獲取枚舉上定義的描述信息在UI上顯示,一個簡單的枚舉定義:public enum EnumGender { ...
  • 一:文件查找1:文件檢索有時候我們因為改bug的需要,必須要知道這個MD5函數在哪些文件中用到了,然而不像cs中我們可以用shift+f12來查找下函數引用,這時候我們就可以用 “文件查找” 解決這個問題。我們可以在 “查找結果” 中清楚的看到哪些文件和哪些行使用到了這個md5函數,然後我們繼續順藤...
  • 首先、導入命名空間:using System.Net.Mail;定義發送電子郵件的方法[網上很多不同的,可以對比著看一下,WinForm的也適用]:/// /// 發送電子郵件/// /// 發件人郵箱地址/// 收件人郵箱地址/// 郵件主題/// 郵件內容/// public bool Send...
  • 1、查找空節點//*[not(text())] 表示內容為空的節點//*[count(*)=0] 表示沒有子節點的節點"//*[count(*)=0 and not(text())]" 空節點,表示既沒有內容,也沒有子節點,但未排除包含屬性的節點
  • 一:Helios是什麼 Helios是一套高性能的Socket通信中間件,使用C#編寫。Helios的開發受到Netty的啟發,使用非阻塞的事件驅動模型架構來實現高併發高吞吐量。Helios為我們大大的簡化了Socket編程,它已經為我們處理好了高併發情況下的解包,粘包,buffer管理等等。 .....
  • Xamarin入門:包括了安裝相關,環境部署,以及一些常見的問題和一些資源。
  • 在 VS2013 下開發的 MVC4 網站,基於 .net 4.5,伺服器是一臺 Windows 2008 R2,運行的時候就報錯了The'targetFramework'attributeintheelementoftheWeb.configfileisusedonlytotargetversio...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...