InvalidOperationException:调用线程无法访问此对象,因为另一个线程拥有它。
可能重复:
调用线程无法访问此对象,因为另一个线程拥有它
错误:
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 { // } }
lblPrimes
从非UI线程访问任何UI元素(此处为lblPrimes
)。 您必须使用线程中的Invoke
来执行此操作。
这是一个很好的教程:
您只能从主线程更新GUI。
在您的worker方法(calculate())中,您正尝试将项添加到列表框中。
lbPrimes.Items.Add(currval.ToString());
这会导致exception。
您正在以非线程安全的方式访问控件。 当一个没有创建控件的线程试图调用它时,你将得到一个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(…)方法,而不是直接尝试将项添加到列表框控件。
问题是您的工作线程正在尝试访问不允许的UI元素。 您获得的例外情况是警告您。 很多时候你甚至都没有。 相反,您的应用程序将无法预测和惊人地失败。
您可以使用Control.Invoke
将委托的执行Control.Invoke
送到UI线程上。 该委托将执行lbPrimes.Items.Add
操作。 但是,在这种情况下我不推荐这种方法。 原因是因为它会减慢工作线程的速度。
我首选的解决方案是让工作线程将currval
添加到ConcurrentQueue
。 然后,UI线程将通过System.Windows.Forms.Timer
定期轮询此集合,以使值出列并将它们放在ListBox
。 与使用Control.Invoke
相比,这有很多优点。
- 它消除了
Invoke
强加的worker和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 } } 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
循环而不是while
循环interrupt
。 如果currval
变得足够大,则线程需要更长的时间来响应中断请求。