国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

C#event線程安全

2019-11-14 13:34:04
字體:
供稿:網(wǎng)友

突然想到有關(guān)C#中使用event特性時關(guān)于線程安全的問題,以前雖然有遵從“復(fù)制引用+null判斷”的模式(盲目地),但沒有深入了解和思考。

為之查詢了資料和實(shí)驗,對此有了進(jìn)一步的理解。

 

一般event使用模式

定義(field-like event):

public event EventHandler Done;

類內(nèi)raise:

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

不禁要問,為何要復(fù)制引用?多線程下表現(xiàn)如何?

 

關(guān)于C#3.0和C#4.0中編譯器對event實(shí)現(xiàn)的整理

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

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

 

對于如此的代碼,

class EventInCS3{    public event EventHandler Done;}

編譯器會將其轉(zhuǎn)換成:

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

有以下幾點(diǎn)值得注意(同注釋編號):

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

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

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

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

 

而在C#4.0中,同步的實(shí)現(xiàn)有了變化,同樣參見同一作者兩年后的 這一篇博文。

編譯器默認(rèn)的add、remove實(shí)現(xiàn),改為使用compare and swap來實(shí)現(xiàn)lock-free同步。值得注意的是,delegate是不可更改的類型,即+=、-=之后,會指向一個新的對象,而不再是原對象(類似string)。

通過IL查看程序集里生成的add_Done、remove_Done,可以發(fā)現(xiàn)端倪,大致會生成如下的代碼:

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相關(guān)的語義變化整理

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

 

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

這么處理的話,我們就能在OnDone方法里復(fù)制引用,判斷null,進(jìn)行調(diào)用。因為此時Done這個標(biāo)識符,代表的是一個EventHandler對象的引用。

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

Done += SomeHandlerMethod;

時,+=實(shí)際是調(diào)用了:

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

在Visual Studio 2015里寫一個普通的、非event的EventHandler的+=運(yùn)算,鼠標(biāo)放在+=上時,顯示的也是這個函數(shù)簽名。C#3.0時即使對event也是這么處理的。

導(dǎo)致我們失去了默認(rèn)add訪問器提供的同步功能。

 

而這一現(xiàn)象在C#4.0中得到了改善。在類內(nèi)部訪問event的標(biāo)識符時,+=、-=操作符就會被認(rèn)為是add、remove的調(diào)用了。

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

void EventInCS4.Done.add 

 

自定義event訪問器

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

此時,在類內(nèi)部訪問event標(biāo)識符,只會被當(dāng)成是訪問event本身。要引發(fā)事件(Done)的話,需訪問對應(yīng)delegate(__Done(this, new EventArgs()))。

 

操作event的正確方式

一般情況下無需自己實(shí)現(xiàn)event,用field-like就好了。

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

 

一些code snippet如:

  1. 通過擴(kuò)展方法來引發(fā)事件:
    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形式得到復(fù)制,所以直接

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

    null-conditional操作符也會進(jìn)行引用的復(fù)制,所以是線程安全的。(沒有Done?(...)這種寫法)

 

對于編譯器是否會將復(fù)制引用作為重復(fù)的局部變量優(yōu)化掉,以至于在一些情況下需要使用諸如以下的方式的問題,我沒有深入了解。

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

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

  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看)

上一篇:編程概念

下一篇:Xamarin入門淺析

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 阳山县| 丹阳市| 吴旗县| 迁西县| 法库县| 如皋市| 菏泽市| 临洮县| 鄱阳县| 周至县| 关岭| 库伦旗| 云梦县| 和平县| 株洲县| 石泉县| 阳东县| 伊通| 喜德县| 土默特左旗| 肇庆市| 岱山县| 武威市| 巴彦县| 隆回县| 长宁区| 洛阳市| 玛纳斯县| 阳新县| 桂阳县| 西充县| 浦北县| 修武县| 盐城市| 固安县| 古田县| 得荣县| 海兴县| 开平市| 黄平县| 织金县|