InvalidOperationException:调用线程无法访问此对象,因为其他线程拥有它

本文关键字:线程 因为 其他 对象 拥有 访问 调用 InvalidOperationException | 更新日期: 2023-09-27 18:08:52

可能重复:
调用线程无法访问此对象,因为其他线程拥有它

错误:

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

是什么导致了这种情况,我该如何解决?

InvalidOperationException:调用线程无法访问此对象,因为其他线程拥有它

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

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

不允许从非UI线程访问任何UI元素(此处为lblPrimes(。您必须使用线程中的Invoke才能做到这一点。

这里有一个很好的教程:

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

您只能从主线程更新GUI。

在worker方法(calculate(((中,您正试图将项添加到列表框中。

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

这会导致异常。

您访问控件的方式不是线程安全的。当一个没有创建控件的线程尝试调用它时,您会得到一个InvalidOperationException。

如果要将项目添加到列表框中,则需要使用CodeKing提到的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);
    }
}

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

问题是您的工作线程试图访问不允许的UI元素。你得到的例外是警告你这一点。很多时候你甚至都不明白。相反,您的应用程序将以不可预测的、引人注目的方式失败。

您可以使用Control.Invoke将委托的执行封送到UI线程上。此委派将执行lbPrimes.Items.Add操作。但是,我不建议在这种情况下采用这种方法。原因是它会减慢工作线程的速度。

我的首选解决方案是让工作线程将currval添加到ConcurrentQueue。然后UI线程将通过System.Windows.Forms.Timer周期性地轮询该集合,以将值出列并将它们放在ListBox中。与使用Control.Invoke相比,这有很多优点。

  • 它消除了Invoke强加的工作线程和UI线程之间的紧密耦合
  • 它将更新UI的责任放在UI线程中,它无论如何都应该属于该线程
  • UI线程可以决定更新的时间和频率
  • 工作线程不必等待UI响应Invoke请求。它将增加工作线程的吞吐量
  • 由于CCD_ 12是一种昂贵的操作,因此它更有效
  • 尝试使用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
  • 您可能应该在for循环中使用interrupt,而不是while循环。如果currval变得足够大,线程将需要越来越长的时间来响应中断请求
相关文章: