4

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?

Community
  • 1
  • 1
test123123
  • 911
  • 3
  • 16
  • 33

4 Answers4

4

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
    {
      //
    }
 }
TheCodeKing
  • 19,064
  • 3
  • 47
  • 70
  • `Calculate` is not the problem here, accessing `lblPrimes` is. – Aliostad Sep 16 '11 at 09:32
  • 1
    Yes exactly which is in calculate, hence the invoke back to self. You could of course do it differently and put the invoke nested deeper, it's just an example. – TheCodeKing Sep 16 '11 at 09:40
  • 1
    This is **suboptimal** and *defeats the objective* of using threads altogether. You do not want the whole calculate to be done in `Invoke`, only the updating of the label. Using your code, thread is not doing anything. – Aliostad Sep 16 '11 at 09:43
  • It really depends entirely on what you are doing, as I say the aim is to show how to solve the problem, not to write the most optimized production ready code for the OP. This a client-side app, it really doesn't make the slightest difference in most cases. I think this solution is easier to understand and maintain. – TheCodeKing Sep 16 '11 at 09:48
2

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

Aliostad
  • 80,612
  • 21
  • 160
  • 208
1

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.

Christophe Geers
  • 8,564
  • 3
  • 37
  • 53
0

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.
Brian Gideon
  • 47,849
  • 13
  • 107
  • 150