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

4

错误:

可能是重复问题:
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个回答

4

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

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

计算不是问题,访问“lblPrimes”才是。 - Aliostad
1
是的,这正是在计算中所涉及的,因此需要调用自身。当然,你也可以选择不同的方式,将调用嵌套得更深,这只是一个例子。 - TheCodeKing
1
这是次优的,而且完全违背了使用线程的目的。你不希望整个计算都在Invoke中完成,只需要更新标签即可。使用你的代码,线程什么也没做。 - Aliostad
这完全取决于你正在做什么,正如我所说的,目标是展示如何解决问题,而不是为OP编写最优化的生产就绪代码。这是一个客户端应用程序,在大多数情况下真的没有任何区别。我认为这个解决方案更容易理解和维护。 - TheCodeKing

2

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

在你的 Calculate() 方法中,调用 AddListBoxItem(...) 方法,而不是直接尝试添加项目到 listbox 控件。

0
问题在于您的工作线程正在尝试访问不允许的UI元素。您收到的异常警告您此事。通常情况下,您甚至都不会得到这个警告。相反,您的应用程序将无法预测地失败。
您可以使用Control.Invoke将委托的执行调度到UI线程上。该委托将执行lbPrimes.Items.Add操作。但是,在这种情况下,我不建议使用这种方法。原因是它会减慢工作线程的速度。
我的首选解决方案是让工作线程将currval添加到ConcurrentQueue中。然后,UI线程将通过System.Windows.Forms.Timer定期轮询此集合以出列值并将其放入ListBox中。这比使用Control.Invoke具有许多优点。
  • 它消除了工作线程和UI线程之间的紧密耦合,这是Invoke所强加的。
  • 它将更新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
  • 你应该在 for 循环中而不是 while 循环中进行 interrupt。如果 currval 变得足够大,线程响应中断请求的时间将变得越来越长。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接