views:

2258

answers:

6

This is baffling me, maybe somebody can shine the light of education on my ignorance. This is in a C# windows app. I am accessing the contents of a listbox from a thread. When I try to access it like this

prgAll.Maximum = lbFolders.SelectedItems.Count;
I get the error. However, here is the part I don't get. If I comment out that line, the very next line
foreach (string dir in lbFolders.SelectedItems)
executes just fine.

Edit: As usual, my communication skills are lacking. Let me clarify.

I know that accessing GUI items from threads other than the ones they were created on causes problems. I know the right way to access them is via delegate.

My question was mainly this: Why can I access and iterate through the SelectedItems object just fine, but when I try to get (not set) the Count property of it, it blows up.

+4  A: 
prgAll.Maximum = lbFolders.SelectedItems.Count;

On that line you perform an assignment (set/add), which by default is not thread-safe.

On the second line it's just a get operation, where thread-safety merely doesn't matter.

EDIT: I don't mean access to the prgAll element.

Accessing the Count property changes the internal state of the ListBox inner collection, that is why it throws the exception.

arul
nope, wrong answer. I broke it up and the error is happening when getting the Count value, not when setting the Maximum value
Kevin
+1 for the remark about inner state. Unfortunately Kevin doesn't like your answer, even though it's true.
OregonGhost
Actually, his edited answer makes much more sense. Changed my -1 to a +1
Kevin
Of course, why M$ designed it like that still doesn't make sense, but that isn't Lykathea's problem
Kevin
I think the reason is that they only test for inter-thread calls when it really matters. Kind of an optimization.
OregonGhost
+3  A: 

The Count property of SelectedItems is not thread-safe, so you can't use it cross-thread.

Martín Marconcini
Thank you, this is the only answer which even begins to answer the question. So, the SelectedItems object is thread-safe, but its property isn't? That doesn't make sense to me. Not saying you are wrong, just that it doesn't make sense.
Kevin
Regardless of it making sense or not, its clear that cross-threading is the issue and you should be invoking back to the gui thread to access those properties. To see if something is thread-safe, look it up in the MSDN library - it clearly states thread-safety.
ScottCher
I actually did look up both the SelectedItems object and the Count property on MSDN, I didn't see anything on the "thread-safeness" of either.
Kevin
+1  A: 

You can't touch a GUI object from a thread that isn't the main GUI thread. See here for more details and the solution.

Matt Warren
OOpps, sorry, didn't mean to remove your comment
Matt Warren
No problem, I'll just re-add it. Your answer doesn't make sense because setting the value of the prgAll.Maximum isn't blowing up, neither does it blow up on accessing the SelectItems itself, just on the selecteditems.count
Kevin
@Kevin: matt's answer makes perfect sense. While you can't generally access GUI elements from another thread, not all properties or methods suffer from this. The rule is quite simple: Don't touch GUI objects in a thread other than the GUI thread. See Echostorms answer for how to do it right.
OregonGhost
@OregonGhost: no, your answer makes sense, at least more sense. His doesn't make sense because he's telling me I can't do something which I obviously can.
Kevin
@OregonGhost. However, it still doesn't make sense to me that I can access ListBox.selecteditems and iterate through it with no problem, but if I try to access ListBox.SelectedItems.Count, it blows up.
Kevin
@Kevin: Lykathea explains why this is the way it is. Maybe you could implement it in another way, but that's how the .NET team did it. Just never access any GUI elements from another thread and you're fine.
OregonGhost
I think it goes back to a Win32 issue (or design decision), which is still there in WinForms because it uses Win32/MFC under the hood
Matt Warren
+1  A: 

You're trying to write to a control from a thread other than the main thread. Use Invoke or BeginInvoke.

void SetMax()
{
    if (prgAll.InvokeRequired)
    {
        prgAll.BeginInvoke(new MethodInvoker(SetMax));
        return;
    }

    prgAll.Maximum = lbFolders.SelectedItems.Count;
}
Jon B
nope, wrong answer. I broke it up and the error is happening when getting the Count value, not when setting the Maximum value
Kevin
@Kevin Any chance you're creating prgAll and lblFolders from different threads?
Jon B
@Kevin using prgAll.Invoke instead of this.Invoke might make that work for you.
Jon B
they are both created in the same thread
Kevin
+4  A: 

You can't access GUI elements from a separate thread. Use a delegate to make the change.

eg.

lblStatus.Invoke((Action)(() => lblStatus.Text = counter.ToString()));

or older skool:

lblTest.Invoke((MethodInvoker)(delegate() 
{ 
  lblTest.Text = i.ToString(); 
}));

I've got a blog post on how to do this in all the .Net releases here.

Echostorm
+1  A: 

Because you created a control in a thread and you're trying to reach it from another one. Call the InvokeRequired property as shown here:

private void RunMe()
{
    if (!InvokeRequired)
    {
        myLabel.Text = "You pushed the button!";
    }
    else
    {
        Invoke(new ThreadStart(RunMe));
    }
}
sebastian