2012-04-02 11 views
4

Dispatcher.Invokeを使用して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> 

私は別のスレッドで新しいタスクを開始、私はUIを更新するためにDispatcher.BeginInvokeを使用する必要がありますしています。

この場合、UIを更新していますが、別のスレッドからDispatcher.InvokeまたはBeginInvokeを使用してUIを更新するシナリオをいくつか見てきました。

私の質問は、私たちがそれをしなければならないときです。なぜこの場合はうまくいきますか?それは実際にあなたのUIを更新しないよう

おかげ&よろしく、 BHavik

答えて

7

私は別のスレッドからUIの 何かを更新するためにDispatcher.Invokeを使用する際について疑問を持っています。

異なるスレッドを使用している場合は、ディスパッチャを使用して別のスレッドに属するuiコンポーネントを更新する必要があります。私は別のスレッドで新しいタスクを始めています

は、私はUIを更新するために Dispatcher.BeginInvokeを使用する必要があります。

タスクでは、呼び出されたスレッドをブロックせずに複数の操作を実行できますが、別のスレッドにあるわけではありません。ただし、タスク内からUIを更新する場合は、ディスパッチャを使用する必要があります。この場合

それはUIを更新している、しかし、私は 人々が 異なるスレッドからDispatcher.InvokeまたはBeginInvokeメソッドを使用してUIを更新するいくつかのシナリオを見てきました。

呼び出しを実行している間に呼び出しスレッドがブロックされ、BeginInvokeはブロックされません。 BeginInvokeは、コントロールを呼び出し元にすぐに返します。Invokeを呼び出すと、処理が重い場合に呼び出しスレッドがハングアップすることがあります。

これは、MSDNのドキュメント、WPFでは

からで、DispatcherObjectにを作成したスレッドだけが そのオブジェクトにアクセスすることができます。たとえば、メインUIスレッド からスピンオフされるバックグラウンドスレッドは、UIスレッドで作成された のボタンの内容を更新できません。バックグラウンドスレッドが のButtonのContentプロパティにアクセスするには、バックグラウンドスレッドはUIスレッドに関連付けられたDispatcherに の作業を委任する必要があります。 これは、InvokeまたはBeginInvokeのいずれかを使用して行います。呼び出しは 同期で、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を使用する必要がありますか? –

+0

リストの種類によって異なります。 'List ':いいえ、しかし、更新はUIに表示されません。 ObservableCollection ':はい。 –

+0

タスクの内部から更新する場合は、Dispatcher.Invoke()を呼び出す必要があります。これを私の答えに上に示すためにテストの完全な例を掲載しました。 – Despertar

6

あなたのテストは有効ではありません。証明が必要な場合は、この睡眠通話を追加してください:

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

あなたのUIが表示され、リストが空であることがわかります。 10秒、30秒、3ヶ月後には、リストにはあなたの文字列は含まれません。

代わりに、あなたのTest()メソッドは、の文字列がリストに追加されるまでに十分速く完了しているので、のUIが画面に表示され、リストが読み込まれます。

修正するには、コレクションをObservableCollection<string>に変更してください。しかし、あなたは次の問題に直面するでしょう - ObservableCollectionをバックグラウンドスレッドで更新することはできません。 Dispatcherの出番だからです。

+0

は非常にあなたの答えを理解していません。 UIスレッドは単に10秒間スリープし、項目「abc」が追加されます。 – KMC