[英]C# Threading - Using a class in a thread-safe way vs. implementing it as thread-safe
假設我想使用.Net Framework
的非線程安全類(文檔指出它不是線程安全的)。 有時我從一個線程更改Property X
的值,有時又從另一個線程更改Property X
的值,但我從未同時從兩個線程訪問它。 有時我從一個線程調用Method Y
,有時又從另一個線程調用Method Y
,但是從不同時調用。
這是否意味着我以線程安全的方式使用該類,並且文檔中指出它不是線程安全的事實與我的情況不再相關?
如果答案是否定的:是否可以在同一個線程中執行與特定對象相關的所有操作-即創建該對象並始終在同一個線程(而不是GUI線程)中調用其成員? 如果是這樣,我該怎么做? (如果相關,則為WPF應用)。
不,它不是線程安全的。 通常,如果不進行某種同步,則永遠不要編寫多線程代碼。 在第一個示例中,即使以某種方式設法確保永遠不會同時進行修改/讀取,仍然存在緩存值和指令重新排序的問題。
例如,CPU將值緩存到寄存器中,您在一個線程上對其進行更新,然后從另一個線程中對其進行讀取。 如果第二個緩存了它,則它不會進入RAM來獲取它,也看不到更新后的值。
看看這篇很棒的文章,了解更多信息以及編寫無鎖多線程代碼鏈接的問題 。 它很好地解釋了CPU,編譯器和CLI字節碼編譯器如何重新排序指令。
假設我想使用.Net Framework中的非線程安全類(文檔指出它不是線程安全的)。
“線程安全”具有許多不同的含義。 大多數對象屬於以下三類之一:
有時我從一個線程更改屬性X的值,有時又從另一個線程更改屬性X的值,但我從未同時從兩個線程訪問它。 有時我從一個線程調用方法Y,有時又從另一個線程調用方法Y,但是從不同時調用。
正如另一個回答者所指出的,您必須考慮指令的重新排序和緩存的讀取。 換句話說,僅僅在不同的時間做這些是不夠的。 您將需要設置適當的障礙,以確保可以正常工作。
最簡單的方法是使用lock
語句保護對象的所有訪問。 如果所有的讀取,寫入和方法調用都在同一個鎖中,那么這將起作用(假設對象確實具有一次一次性的線程模型而不是線程仿射)。
假設我想使用.Net Framework中的非線程安全類(文檔指出它不是線程安全的)。 有時我從一個線程更改屬性X的值,有時又從另一個線程更改屬性X的值,但我從未同時從兩個線程訪問它。 有時我從一個線程調用方法Y,有時又從另一個線程調用方法Y,但是從不同時調用。
默認情況下,所有Classes
都是非線程安全的,只有少數幾個Collections
例如專門為thread safety
設計的Concurrent Collections
除外。 因此,對於您可能選擇的任何其他類,如果您通過multiple threads
或以Non atomic
方式訪問它,則無論是read / write
,都必須在更改對象狀態時引入線程安全性。 這僅適用於狀態可以在多線程環境中修改的對象,但是方法本身僅是函數實現,它們本身不是可以修改的狀態,它們只是引入線程安全性以維護對象狀態。
這是否意味着我以線程安全的方式使用該類,並且文檔中指出它不是線程安全的事實與我的情況不再相關? 如果答案是否定的:我可以在同一個線程(而不是GUI線程)中完成所有與類相關的事情嗎? 如果是這樣,我該怎么做? (如果相關,則為WPF應用)。
對於Ui應用程序,請考慮為基於IO的操作引入Async-Await
,例如文件讀取,數據庫讀取以及將TPL
用於計算綁定操作。 Async-Await
好處是:
await
Ui控件可以直接更新而無需跨線程問題,因為僅涉及一個線程 最后:有些類中的一種方法開始一個操作,另一種方法結束一個操作。 例如,使用SpeechRecognitionEngine類,可以使用RecognizeAsync(此方法在TPL庫之前,因此它不返回Task)啟動語音識別會話,然后使用RecognizeAsyncCancel取消識別會話。 如果我從一個線程調用RecognizeAsync並從另一個線程調用RecognizeAsyncCancel怎么辦? (它可以工作,但是“安全”嗎?在某些我不知道的情況下會失敗嗎?)
正如您提到的Async
方法,這可能是基於APM
的較舊的實現,它需要AsyncCallBack
進行協調,在BeginXX, EndXX
的一行上BeginXX, EndXX
(如果是這樣的話),則不需要太多協調工作,因為他們使用AsyncCallBack執行回調委托。 實際上,如前所述,這里沒有涉及額外的線程,無論是舊版本還是新版本的Async-Await
。 關於任務取消, CancellationTokenSource
可以用於Async-Await
,不需要單獨的取消任務。 多個線程之間的協調可以通過自動/手動ResetEvent完成。 如果上述調用是同步的,則使用Task包裝器返回Task可以通過Async方法調用它們,如下所示:
await Task.Run(() => RecognizeAsync())
雖然它是一種反模式,但在使整個呼叫鏈Async
很有用
編輯(回答OP問題)
感謝您的詳細回答,但我聽不懂。 首先,您說的是“必須引入線程安全性”,但是如何?
lock, mutex, semaphore, monitor, Interlocked,
)引入的,它們全部用於保存對象,防止其損壞/競爭。 我看不到任何步驟。 如我的帖子所述,我采取的步驟是否足夠?
第二點,我問如何始終在同一線程中使用對象(無論何時使用)。 AFAIK與Async-Await無關。
IO completion ports
(基於硬件的並發),否則,如果使用Task Parallel library
,那么您將無法確保始終使用相同/給定的線程,因為這是非常高級的抽象 在這里查看我最近關於線程的詳細答案之一,它可能有助於提供一些更詳細的方面
由於存在技術風險,因此它不是線程安全的,但是您的策略旨在解決該問題並解決該風險。 因此,如果一切按您的描述進行,那么您將沒有線程安全的環境,但是,您是安全的。 目前。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.