簡體   English   中英

使用dispatcher.Invoke是否會使我的線程安全?

[英]Does using dispatcher.Invoke make my thread safe?

在我的WPF應用程序中,我運行了長時間運行的上載程序,它會在運行過程中引發進度事件,從而更新進度條。 用戶還可以取消上傳,否則可能會出錯。 這些都是異步事件,因此需要使用Dispatcher.Invoke執行它們以更新UI。

因此,ish的代碼如下所示:

void OnCancelButtonClicked(object sender, EventArgs e)
{
    upload.Cancel();
    _cancelled = true;
    view.Close();
    view.Dispose();
}

void OnProgressReceived(object sender, EventArgs<double> e)
{
    Dispatcher.Invoke(() => 
    {
        if (!cancelled)
            view.Progress = e.Value;
    }
}

假設設置視圖。在已配置視圖上的進度將引發錯誤,此代碼線程安全嗎? 例如,如果用戶在進度更新時單擊“取消”,則他/她將不得不等到進度已更新,並且如果在執行OnCancelButtonClicked期間更新了進度,則Dispatcher.Invoke調用將導致view.Progress更新為設置_cancelled之后直到被排隊,所以在那里我不會有問題。

還是為了安全起見,我需要一把鎖嗎?

object myLock = new object();

void OnCancelButtonClicked(object sender, EventArgs e)
{
    lock(myLock)
    {
        upload.Cancel();
        _cancelled = true;
        view.Close();
        view.Dispose();
    }
}

void OnProgressReceived(object sender, EventArgs<double> e)
{
    Dispatcher.Invoke(() => 
    {
        lock(myLock)
        {
            if (!cancelled)
                view.Progress = e.Value;
        }
    }
}

您不必添加鎖。 Dispatcher.Invoke和BeginInvoke請求將不會在其他代碼中間運行(這是它們的全部重點)。

僅需考慮兩件事:

  1. 在這種情況下,BeginInvoke可能更合適,Invoke會將請求排隊,然后阻塞調用線程,直到UI線程變為空閑並完成代碼執行為止,BeginInvoke只會將請求排隊而不會阻塞。
  2. 某些操作,特別是打開窗口(包括消息框)或進行進程間通信的操作,可能允許排隊的調度程序運行。

編輯:首先,我沒有被引用,因為不幸的是,關於該主題的MSDN頁面的詳細信息很低-但是我編寫了測試程序來檢查BeginInvoke的行為,我在這里編寫的所有內容都是這些測試的結果。

現在,為了擴展第二點,我們首先需要了解調度程序的功能。 顯然,這是一個非常簡化的解釋。

任何Windows UI都可以通過處理消息來工作。 例如,當用戶將鼠標移到窗口上時,系統將向該窗口發送WM_MOUSEMOVE消息。

系統通過將消息添加到隊列中來發送消息,每個線程可能有一個隊列,同一線程創建的所有窗口共享同一隊列。

在每個Windows程序的心臟中,都有一個稱為“消息循環”或“消息泵”的循環,該循環從隊列中讀取下一條消息,並調用適當的窗口代碼來處理該消息。

在WPF中,此循環以及分派器處理的所有相關處理。

應用程序可以在消息循環中等待下一條消息,也可以在執行某些操作。 這就是為什么當您進行長時間的計算時,所有線程的窗口都不響應-線程正在忙於工作,並且不會返回到消息循環來處理下一條消息。

Dispatcher.Invoke和BeginInvoke的工作方式是使請求的操作排隊並在線程下次返回消息循環時執行該操作。

這就是為什么Dispatcher。(Begin)Invoke不能在方法中間“注入”代碼的原因,直到方法返回之前,您不會回到消息循環。

任何代碼都可以運行消息循環。 當您調用運行消息循環的任何程序時,將調用Dispatcher並可以運行(Begin)Invoke操作。

哪種代碼具有消息循環?

  1. 具有GUI或接受用戶輸入的任何內容,例如對話框,消息框,拖放等-如果這些窗口沒有消息循環,則該應用程序將無響應並且無法處理用戶輸入。
  2. 使用幕后消息的進程間通信(大多數進程間通信方法(包括COM)都使用它們)。
  3. 其他所有需要很長時間且不會凍結系統的事情(系統未凍結的速度很快就證明了它正在處理消息)。

因此,總結一下:

  • Dispatcher不能只將代碼放入線程中,而只能在應用程序處於“消息循環”中時執行代碼。
  • 您編寫的任何代碼都沒有消息循環,除非您明確地編寫了它們。
  • 大多數用戶界面代碼都沒有自己的消息循環,例如,如果您調用Window.Show然后進行一些長時間的計算,則只有在計算完成並且方法返回后,窗口才會出現(應用程序返回到消息循環並處理打開和繪制窗口所需的所有消息)。
  • 但是,任何在返回之前與用戶交互的代碼(MessageBox.Show,Window.ShowDialog)都必須具有消息循環。
  • 某些通信代碼(網絡和進程間)使用消息循環,而某些則不使用,這取決於您使用的特定實現。

這是個有趣的問題。 在調度程序中執行的項目被排隊,並在與 UI交互相同的線程上執行 這是關於此主題的最佳文章: http : //msdn.microsoft.com/zh-cn/library/ms741870.aspx

如果我冒險猜測,我會說Dispatcher.Invoke(Action)可能會排隊一個原子工作項 ,因此可能沒問題,但是我不確定它是否將您的UI事件處理程序包裝在原子中動作項目,例如:

//Are these bits atomic?  Not sure.
upload.Cancel();
_cancelled = true;

為了安全起見,我會親自鎖定,但是您的問題值得進一步研究。 可能需要潛水反射器才能確定。

順便說一句,我可能會優化您的鎖。

Dispatcher.Invoke(() => 
{
     if (!cancelled)
     {
          lock(myLock)
          {
               if(!cancelled)
                    view.Progress = e.Value;
          }
     }
}

但這可能是矯kill過正:)

暫無
暫無

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

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