InvalidOperationException: The calling thread cannot access this object because a different thread owns it. [duplicate]
Possible Duplicate:
The calling thread cannot access this object because a different thread owns it
Error:
The calling thread cannot access this object because a different thread owns it.
Code:
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++;
}
}
}
What would be causing this, and how can I resolve it?
You need to rejoin the main UI thread in order to affect the UI. You can check whether this is needed with InvokeRequired, and implement Invoke before referencing the controls.
private void calculate()
{
if (InvokeRequired)
{
Invoke(new Action(() => calculate()));
}
else
{
//
}
}
Accessing any UI element (lblPrimes
here) from a non-UI thread is not allowed. You have to use Invoke
from your thread to do that.
Here is a good tutorial:
http://weblogs.asp.net/justin_rogers/pages/126345.aspx
You can only update the GUI from the main thread.
In your worker method (calculate()) you are trying to add items to a listbox.
lbPrimes.Items.Add(currval.ToString());
This causes the exception.
You are accessing the control in a manner that is not thread safe. When a thread that did not create the control tries to call it, you'll get an InvalidOperationException.
If you want to add items to the listbox you need to use InvokeRequired as TheCodeKing mentioned.
For example:
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);
}
}
Call this AddListBoxItem(...) method within your Calculate() method instead of directly trying to add items to the listbox control.
The problem is that your worker thread is attempting to access a UI element which is not allowed. The exception you are getting is warning you about this. Often times you do not even get that. Instead your application will fail unpredictably and spectacularly.
You could use Control.Invoke
to marshal the execution of a delegate onto the UI thread. This delegate would perform the lbPrimes.Items.Add
operations. However, I do not recommend this approach in this case. The reason is because it will slow down the worker thread.
My preferred solution would be to have the worker thread add currval
to a ConcurrentQueue
. Then the UI thread will periodically poll this collection via a System.Windows.Forms.Timer
to dequeue the values and place them in the ListBox
. This has a lot of advantages over using Control.Invoke
.
- It removes the tight coupling between the worker and UI threads that
Invoke
imposes. - It puts the responsibility of updating the UI in the UI thread where it should belong anyway.
- The UI thread gets to dictate when and how often the update takes place.
- The worker thread does not have to wait for the UI to respond to the
Invoke
request. It will increase the throughput on the worker thread. - It is more efficient since
Invoke
is costly operation. - Many of the subtle race conditions that arise when attempting to a terminate a worker thread using
Invoke
naturally go away.
Here is how my preferred option might look.
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());
}
}
I noticed a couple of other problems.
Thread.Interrupt
unblocks the BCL waiting calls likeWaitOne
,Join
,Sleep
, etc. Your usage of it serves no purpose. I think what you want to do instead is setinterrupt = true
.- You should probably
interrupt
in thefor
loop instead of thewhile
loop. Ifcurrval
gets big enough it will take longer and longer for the thread to respond to the interrupt request.
精彩评论