开发者

InvalidOperationException: The calling thread cannot access this object because a different thread owns it. [duplicate]

This question already has answers here: Closed 10 years ago.

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 like WaitOne, Join, Sleep, etc. Your usage of it serves no purpose. I think what you want to do instead is set interrupt = true.
  • You should probably interrupt in the for loop instead of the while loop. If currval gets big enough it will take longer and longer for the thread to respond to the interrupt request.
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜