簡體   English   中英

C#線程-以線程安全的方式使用類與將其實現為線程安全的

[英]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中的非線程安全類(文檔指出它不是線程安全的)。

“線程安全”具有許多不同的含義。 大多數對象屬於以下三類之一:

  • 仿射。 這些對象只能從單個線程訪問,而不能從另一個線程訪問。 大多數UI組件都屬於此類。
  • 線程安全的。 可以隨時從任何線程訪問這些對象。 大多數同步對象(包括並發集合)都屬於此類。
  • 一次一個。 可以一次從一個線程訪問這些對象。 這是“默認”類別,大多數.NET類型都屬於該類別。

有時我從一個線程更改屬性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好處是:

  • 它根本不會阻塞Ui線程,並保持Ui完全響應,實際上, await Ui控件可以直接更新而無需跨線程問題,因為僅涉及一個線程
  • TPL並發性也使計算操作受阻,它們從線程池中召喚線程,並且由於跨線程問題而不能用於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問題)

感謝您的詳細回答,但我聽不懂。 首先,您說的是“必須引入線程安全性”,但是如何?

  1. 線程安全是通過使用同步結構(如lock, mutex, semaphore, monitor, Interlocked, )引入的,它們全部用於保存對象,防止其損壞/競爭。 我看不到任何步驟。

如我的帖子所述,我采取的步驟是否足夠?

  1. 我在您的帖子中沒有看到任何線程安全步驟,請突出顯示您正在談論的步驟

第二點,我問如何始終在同一線程中使用對象(無論何時使用)。 AFAIK與Async-Await無關。

  1. Async-Await是唯一的並發機制,由於它不使用任何其他線程來調用線程,因此可以確保所有內容始終在同一線程上運行,因為它使用IO completion ports (基於硬件的並發),否則,如果使用Task Parallel library ,那么您將無法確保始終使用相同/給定的線程,因為這是非常高級的抽象

這里查看我最近關於線程的詳細答案之一,它可能有助於提供一些更詳細的方面

由於存在技術風險,因此它不是線程安全的,但是您的策略旨在解決該問題並解決該風險。 因此,如果一切按您的描述進行,那么您將沒有線程安全的環境,但是,您是安全的。 目前。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM