InvalidOperationException:调用线程无法访问此对象,因为另一个线程拥有它。

时间:2011-09-16 09:24:08

标签: c# wpf multithreading

  

可能重复:
  The calling thread cannot access this object because a different thread owns it

错误:

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 个答案:

答案 0 :(得分:4)

您需要重新加入主UI线程才能影响UI。您可以使用InvokeRequired检查是否需要这样做,并在引用控件之前实现Invoke。

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

答案 1 :(得分:2)

不允许从非UI线程访问任何UI元素(此处为lblPrimes)。您必须使用线程中的Invoke来执行此操作。

这是一个很好的教程:

http://weblogs.asp.net/justin_rogers/pages/126345.aspx

答案 2 :(得分:1)

您只能从主线程更新GUI。

在您的工人方法(calculate())中,您正尝试将项目添加到列表框中。

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

这会导致异常。

您正在以非线程安全的方式访问控件。当一个没有创建控件的线程试图调用它时,你将得到一个InvalidOperationException。

如果要将项目添加到列表框,则需要使用InvokeRequired作为TheCodeKing。

例如:

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);
    }
}

在Calculate()方法中调用此AddListBoxItem(...)方法,而不是直接尝试将项目添加到列表框控件。

答案 3 :(得分:0)

问题是您的工作线程正在尝试访问不允许的UI元素。您获得的例外情况是警告您。很多时候你甚至都没有。相反,您的应用程序将无法预测且非常惊人地失败。

您可以使用Control.Invoke将委托的执行封送到UI线程上。该代表将执行lbPrimes.Items.Add操作。但是,在这种情况下我不推荐这种方法。原因是它会减慢工作线程。

我首选的解决方案是让工作线程将currval添加到ConcurrentQueue。然后,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取消阻止等待来电的BCL,例如WaitOneJoinSleep等。您对它的使用毫无意义。我认为您要做的是设置interrupt = true
  • 您应该在interrupt循环中for而不是while循环。如果currval变得足够大,则线程需要更长的时间来响应中断请求。