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++;
}
}
}
是什么导致了这种情况,我该如何解决?
您需要重新加入主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等待呼叫,如WaitOne
、Join
、Sleep
等。您使用它没有任何用途。我认为您要做的是设置interrupt = true
- 您可能应该在
for
循环中使用interrupt
,而不是while
循环。如果currval
变得足够大,线程将需要越来越长的时间来响应中断请求