.NET:如何在特定執行緒上呼叫委託?(ISynchronizeInvoke、Dispatcher、AsyncOperation、SynchronizationContext 等)
首先請注意,此問題未標記為winforms或wpf或任何其他特定於 GUI 的內容。這是有意的,你很快就會看到。
其次,對不起,如果這個問題有點長。我試圖將到處飄蕩的各種資訊匯集在一起,以提供有價值的資訊。然而,我的問題就在“我想知道的”下面。
我的任務是最終了解 .NET 提供的在特定執行緒上呼叫委託的各種方法。
我想知道的:
- 我正在尋找最通用的方式(不是 Winforms 或 WPF 特定的)來呼叫特定執行緒上的委託。
- 或者,換種說法:我會感興趣,如果以及如何,不同的方式來做到這一點(例如通過 WPF’s
Dispatcher)相互利用;也就是說,如果所有其他人都使用一種通用的跨執行緒委託呼叫機制。我已經知道的:
有很多與這個主題相關的課程;其中:
SynchronizationContext(inSystem.Threading)如果我不得不猜測,那將是最基本的一個;雖然我不明白它到底是做什麼的,也不知道它是如何使用的。
AsyncOperation& (in ) 這些似乎是. 不知道如何使用它們。AsyncOperationManagerSystem.ComponentModelSynchronizationContextWindowsFormsSynchronizationContext(inSystem.Windows.Forms)的子類
SynchronizationContext。
ISynchronizeInvoke(inSystem.ComponentModel)由 Windows 窗體使用。(
Control該類實現了這一點。如果我不得不猜測,我會說這個實現使用了WindowsFormsSynchronizationContext.)
Dispatcher& (in ) 似乎後者是 的另一個子類,而前者代表它。DispatcherSynchronizationContextSystem.Windows.ThreadingSynchronizationContext一些執行緒有自己的消息循環以及消息隊列。
(關於消息和消息隊列的 MSDN 頁面有一些關於消息循環如何在系統級別工作的介紹性背景資訊,即作為 Windows API 的消息隊列。)
我可以看到如何為帶有消息隊列的執行緒實現跨執行緒呼叫。
PostThreadMessage使用 Windows API,您可以通過包含呼叫某個委託的指令將消息放入特定執行緒的消息隊列中。在該執行緒上執行的消息循環最終將獲得該消息,並且將呼叫委託。根據我在 MSDN 上閱讀的內容,執行緒不會自動擁有自己的消息隊列。例如,當一個執行緒創建了一個視窗時,一個消息隊列將變得可用。沒有消息隊列,執行緒有消息循環是沒有意義的。
那麼,當目標執行緒沒有消息循環時,是否可以進行跨執行緒委託呼叫?比方說,在 .NET 控制台應用程序中?(從這個問題的答案來看,我想控制台應用程序確實不可能。)
很抱歉發布這麼長的答案。但我認為值得解釋到底發生了什麼。
啊哈!我想我已經弄清楚了。在特定執行緒上呼叫委託的最通用方式確實似乎是
SynchronizationContext類。首先,.NET 框架沒有提供一種預設方式來簡單地將委託“發送”到任何執行緒,這樣它就會立即在那裡執行。顯然,這是行不通的,因為這意味著“中斷”該執行緒當時正在執行的任何工作。因此,目標執行緒自己決定如何以及何時“接收”委託;也就是說,這個功能必須由程序員提供。
因此,目標執行緒需要某種“接收”委託的方式。這可以通過許多不同的方式來完成。一種簡單的機制是執行緒總是返回到某個循環(我們稱之為“消息循環”),它會在其中查看隊列。它會解決隊列中的任何問題。當涉及到與 UI 相關的內容時,Windows 本身就是這樣工作的。
下面,我將展示如何實現一個消息隊列和一個
SynchronizationContextfor it,以及一個帶有消息循環的執行緒。最後,我將展示如何在該執行緒上呼叫委託。例子:
第 1 步。讓我們首先創建一個
SynchronizationContext將與目標執行緒的消息隊列一起使用的類:class QueueSyncContext : SynchronizationContext { private readonly ConcurrentQueue<SendOrPostCallback> queue; public QueueSyncContext(ConcurrentQueue<SendOrPostCallback> queue) { this.queue = queue; } public override void Post(SendOrPostCallback d, object state) { queue.Enqueue(d); } // implementation for Send() omitted in this example for simplicity's sake. }基本上,這只是將通過的所有委託添加
Post到使用者提供的隊列中。(Post是非同步呼叫的方法。Send將用於同步呼叫。我現在省略後者。)**第 2 步。現在讓我們為等待委託到達的執行緒Z
d**編寫程式碼:SynchronizationContext syncContextForThreadZ = null; void MainMethodOfThreadZ() { // this will be used as the thread's message queue: var queue = new ConcurrentQueue<PostOrCallDelegate>(); // set up a synchronization context for our message processing: syncContextForThreadZ = new QueueSyncContext(queue); SynchronizationContext.SetSynchronizationContext(syncContextForThreadZ); // here's the message loop (not efficient, this is for demo purposes only:) while (true) { PostOrCallDelegate d = null; if (queue.TryDequeue(out d)) { d.Invoke(null); } } }**步驟 3.**執行緒Z需要在某處啟動:
new Thread(new ThreadStart(MainMethodOfThreadZ)).Start();第 4 步。最後,回到其他執行緒A,我們想向執行緒Z發送一個委託:
void SomeMethodOnThreadA() { // thread Z must be up and running before we can send delegates to it: while (syncContextForThreadZ == null) ; syncContextForThreadZ.Post(_ => { Console.WriteLine("This will run on thread Z!"); }, null); }這樣做的好處是,
SynchronizationContext無論您是在 Windows 窗體應用程序、WPF 應用程序還是您自己設計的多執行緒控制台應用程序中,它都能正常工作。Winforms 和 WPF 都SynchronizationContext為其主/UI 執行緒提供和安裝合適的 s。在特定執行緒上呼叫委託的一般過程如下:
- 您必須擷取目標執行緒的 ( Z )
SynchronizationContext,以便您可以Send(同步)或Post(非同步)委託給該執行緒。如何做到這一點的方法是儲存SynchronizationContext.Current當你在目標執行緒Z時返回的同步上下文。(此同步上下文必須先前已線上程 Z 上/由執行緒Z註冊。)然後將該引用儲存線上程A可訪問的某個位置。- 線上程A上,您可以使用擷取的同步上下文將任何委託發送或發佈到執行緒Z:
zSyncContext.Post(_ => { ... }, null);