2012-04-02 63 views
4

我對使用Dispatcher.Invoke在不同線程的UI上更新UI的時間有疑問。UI線程上的Dispatcher.Dispatch

這裏是我的代碼...

public Window4() 
    { 
     InitializeComponent(); 
     this.DataContext = this; 

     Task.Factory.StartNew(() => Test()); 
    } 

    private List<string> listOfString = new List<string>(); 

    public List<string> ListOfString 
    { 
     get { return listOfString; } 
     set { listOfString = value; } 
    } 

    public void Test() 
    { 
     listOfString.Add("abc"); 
     listOfString.Add("abc"); 
     listOfString.Add("abc"); 
    } 

<Grid> 
    <ListView ItemsSource="{Binding ListOfString}" /> 
</Grid> 

我在不同的線程開始一個新的任務,我需要使用Dispatcher.BeginInvoke更新UI。

在這種情況下,它正在更新用戶界面,但我已經看到一些場景,其中人們使用Dispatcher.Invoke或BeginInvoke從不同的線程更新UI。

所以我的問題是當我們必須這樣做,爲什麼在這種情況下它工作正常。因爲它實際上不是更新你的UI

感謝&問候, BHavik

回答

7

我有一個關於何時使用Dispatcher.Invoke更新 東西就從UI線程不同的疑問。

當您在不同的線程上時,您將始終必須使用調度程序更新屬於另一個線程的ui組件。

我在不同的線程上啓動一個新的任務,我是否需要使用 Dispatcher.BeginInvoke來更新UI。

任務允許執行多個操作而不阻塞它們被調用的線程,但這並不意味着它們位於不同的線程上。但是,當從任務內更新UI時,您將需要使用調度程序。

在這種情況下,它更新UI,但我看到的一些情形 人使用Dispatcher.Invoke或BeginInvoke的從 不同的線程更新UI。

調用將在執行動作時阻止調用線程,而BeginInvoke不會。 BeginInvoke將立即返回控制給調用者,Invoke可能會導致調用線程掛起,如果它正在執行繁重的操作。

這是從MSDN文檔,

在WPF中,只有創建DispatcherObject線程可以訪問 該對象。例如,主UI線程從 分離出的後臺線程無法更新在UI線程上創建的 的Button內容。爲了後臺線程訪問Button的Content屬性,後臺線程必須將該工作委託給與UI線程關聯的Dispatcher。 這是通過使用Invoke或BeginInvoke完成的。 Invoke是 同步和BeginInvoke是異步的。

編輯:爲了迴應您的評論,我跑了一些測試。

從任務調用Test()(不使用調度程序)時,出現此錯誤「調用線程無法訪問此對象,因爲不同的線程擁有它。」

所以我創建了一個名爲PrintThreadID()的方法。我在進入任務之前打印線程,然後從任務內部進行報告,並且報告兩者都在相同線程 ID上運行。

錯誤是誤導性的,因爲它表示調用線程不同於PrintThreadID()函數顯示的擁有該線程的線程不是真的,它們實際上在同一個線程中。如果不使用Dispather.Invoke(),則同一線程中的任務仍不能更新UI組件。

所以這裏是一個工作示例,它將從任務更新網格。


public partial class MainWindow : Window 
{ 
    public List<string> myList { get; private set; } 

    public MainWindow() 
    { 
     InitializeComponent(); 
     myList = new List<string>(); 
     label1.Content = Thread.CurrentThread.ManagedThreadId.ToString(); 

     Task.Factory.StartNew(PrintThreadID); 
     Task.Factory.StartNew(Test); 

    } 

    private void PrintThreadID() 
    { 
     label1.Dispatcher.Invoke(new Action(() => 
      label1.Content += "..." + Thread.CurrentThread.ManagedThreadId.ToString())); 
    } 

    private void Test() 
    { 
     myList.Add("abc"); 
     myList.Add("abc"); 
     myList.Add("abc"); 

     // if you do not use the dispatcher you will get the error "The calling thread cannot access this object because a different thread owns it." 


     dataGrid1.Dispatcher.Invoke(new Action(() => 
     { 
      dataGrid1.ItemsSource = myList.Select(i => new { Item = i }); 
     })); 
    } 
} 
+1

但是,如果我正在更新或添加綁定到UI的列表中,是否仍然需要使用Dispatcher的Invoke或BeginInvoke ... – 2012-04-02 16:47:12

+0

取決於列表類型。 '列表':不,但是更新不會出現在用戶界面中。 'ObservableCollection ':是的。 – 2012-04-02 16:53:40

+0

當從任務內部更新它時,是的,你必須調用Dispatcher.Invoke(),我發佈了我的測試的完整示例,以在我的答案中顯示上述內容。 – Despertar 2012-04-02 17:23:46

6

您的測試是無效的。如果您需要證明,請添加此睡眠呼叫:

public void Test() 
{ 
    Thread.Sleep(10000); 
    listOfString.Add("abc"); 
    listOfString.Add("abc"); 
    listOfString.Add("abc"); 
} 

您會發現您的用戶界面顯示並且列表爲空。 10秒30秒,3個月後,該列表將不包含您的字符串。

而不是你的測試證明一個競爭條件 - 你的測試()方法完成速度不夠快的字符串被添加到列表中前的UI出現在屏幕上,並讀取列表。

要修復它,請將您的收藏集更改爲ObservableCollection<string>。但是,那麼你會遇到下一個問題 - 你不能在後臺線程上更新ObservableCollection。所以,這也正是Dispatcher進來。

+0

不太明白你的答案。 UI線程會簡單地休眠10秒,並添加項目「abc」。 – KMC 2017-07-15 07:25:37