2011-09-16 7 views
4

Possible Duplicate:
The calling thread cannot access this object because a different thread owns itInvalidOperationException:呼び出し元のスレッドは、別のスレッドがそのオブジェクトを所有しているため、このオブジェクトにアクセスできません。

エラー:

The calling thread cannot access this object because a different thread owns it. 

コード:

public partial class MainWindow : Window 
    { 
     Thread t; 
     bool interrupt; 
     public MainWindow() 
     { 
      InitializeComponent(); 
     } 

     private void btss_Click(object sender, RoutedEventArgs e) 
     { 
      if (t == null) 
      { 
       t = new Thread(this.calculate); 
       t.Start(); 
       btss.Content = "Stop"; 
      } 
      else 
      { 
       t.Interrupt(); 
      } 

     } 

     private void calculate() 
     { 
      int currval = 2; 
      int devide = 2; 
      while (!interrupt) 
      { 
       for (int i = 2; i < currval/2; i++) 
       { 
        if (2 % i != 0) 
        { 
         lbPrimes.Items.Add(currval.ToString()); //Error occures here 
        } 
       } 
       currval++; 
      } 
     } 
    } 

これを引き起こしているだろう、とどのように私はそれを解決することができますか?

答えて

4

UIに影響を与えるには、メインのUIスレッドに再度参加する必要があります。これがInvokeRequiredで必要かどうかを確認し、コントロールを参照する前にInvokeを実装することができます。

private void calculate() 
{ 
    if (InvokeRequired) 
    { 
     Invoke(new Action(() => calculate())); 
    } 
    else 
    { 
     // 
    } 
} 
+0

ここで 'Calculate'は問題ではないので、' lblPrimes'にアクセスしてください。 – Aliostad

+1

はい、正確には計算されているので、自己に戻ります。もちろん、それを別の方法で行い、呼び出しをネストして深く入れてもかまいません。単なる例です。 – TheCodeKing

+0

これは**準最適です**と*はスレッドを完全に使用する目的を破ります。 'Invoke'で計算全体が行われることは望ましくなく、ラベルの更新だけです。あなたのコードを使って、スレッドは何もしていません。 – Aliostad

1

あなたが唯一のメインスレッドからGUIを更新することができます。ここでは

は良いチュートリアルです。

あなたのワーカー・メソッド(calculate())で、リストボックスに項目を追加しようとしています。

lbPrimes.Items.Add(currval.ToString()); 

これは例外です。

スレッドセーフではない方法でコントロールにアクセスしています。コントロールを作成しなかったスレッドがコントロールを呼び出そうとすると、InvalidOperationExceptionが発生します。

リストボックスに項目を追加する場合は、前述のTheCodeKingとしてInvokeRequiredを使用する必要があります。例えば

private delegate void AddListItem(string item); 

private void AddListBoxItem(string item) 
{ 
    if (this.lbPrimes.InvokeRequired) 
    { 
     AddListItem d = new AddListItem(item); 
     this.Invoke(d, new object[] { item}); 
    } 
    else 
    { 
     this.lbPrimes.Items.Add(item); 
    } 
} 

コールあなたの計算()メソッドの代わりに、直接、ListBoxコントロールに項目を追加しようとしている内に、このAddListBoxItem(...)メソッド。

0

問題は、ワーカースレッドが許可されていないUI要素にアクセスしようとしていることです。例外については、これについて警告しています。しばしばあなたはそれを取得していません。代わりに、アプリケーションは予期せず、壮観に失敗します。

Control.Invokeを使用して、デリゲートの実行をUIスレッドにマーシャリングすることができます。この代理人はlbPrimes.Items.Add操作を実行します。ただし、この場合はこの方法をお勧めしません。理由は、ワーカースレッドが遅くなるためです。

私の好ましい解決策は、ワーカースレッドにConcurrentQueuecurrvalを追加することです。次に、UIスレッドはSystem.Windows.Forms.Timerで定期的にこのコレクションをポーリングし、値をデキューしてListBoxに配置します。これには、Control.Invokeを使用することよりも多くの利点があります。

  • Invokeが課すワーカースレッドとUIスレッドの間の緊密な結合が削除されます。
  • UIの更新はUIスレッドに任せられます。
  • UIスレッドは、更新がいつどのくらいの頻度で行われるかを指示します。
  • ワーカースレッドは、UIがInvoke要求に応答するのを待つ必要はありません。ワーカースレッドのスループットが向上します。
  • Invokeが高価な操作であるので、より効率的です。
  • Invokeを使用してワーカースレッドを終了しようとするときに発生する微妙な競合状態の多くは、自然になくなります。

私の好みのオプションがどのように見えるかは次のとおりです。

private void calculate() 
{ 
    int currval = 2; 
    int devide = 2; 
    while (!interrupt) 
    { 
    for (int i = 2; i < currval/2; i++) 
    { 
     if (2 % i != 0) 
     { 
     queue.Add(currval); // ConcurrentQueue<int> 
     } 
    } 
    currval++; 
    } 
} 

private void Timer_Tick(object sender, EventArgs args) 
{ 
    int value; 
    while (queue.TryDequeue(out value)) 
    { 
    lbPrimes.Items.Add(value.ToString()); 
    } 
} 

私は他のいくつかの問題に気付きました。

  • Thread.Interruptそれのあなたの使用量は目的を達成しませんなどWaitOneJoinSleep、のようなBCL待機中のコールのブロックを解除。代わりにあなたがしたいことはinterrupt = trueに設定されていると思います。
  • whileループではなく、forループでinterruptになるはずです。 currvalが十分に大きくなると、スレッドが割り込み要求に応答するのに時間がかかります。
関連する問題