Threads and UI Components

F

Falcon

Hi all,
I want to fill a TreeView with some heave data. this action may take
several minutes and I'd like to show to the user a model dialog with a
simple ProgressBar ('Marquee' style). I managed to work before with
BackgroundWorker and Invoke methods, but this time When I try to add a
node to the tre I alwayes get an Error saying : "Action being
performed on this control is being called from the wrong thread.
Marshal to the correct thread using Control.Invoke or
Control.BeginInvoke to perform this action." , and when I use the
invoke methodology It says that this thread cannot access component
(thr TreeView component) because it was spawn on a diffrent thread.
My TreeView was created on my main form, and my guess is that the main
thread is locking this control along with other controls cause when I
tried to access a TextBox on the main Form from a diffrent thread,
same messages occured.

Am I wrong? in window forms can I access component from other threads
they were not created in?
 
J

Jon Skeet [C# MVP]

I want to fill a TreeView with some heave data. this action may take
several minutes and I'd like to show to the user a model dialog with a
simple ProgressBar ('Marquee' style). I managed to work before with
BackgroundWorker and Invoke methods, but this time When I try to add a
node to the tre I alwayes get an Error saying : "Action being
performed on this control is being called from the wrong thread.
Marshal to the correct thread using Control.Invoke or
Control.BeginInvoke to perform this action." , and when I use the
invoke methodology It says that this thread cannot access component
(thr TreeView component) because it was spawn on a diffrent thread.
My TreeView was created on my main form, and my guess is that the main
thread is locking this control along with other controls cause when I
tried to access a TextBox on the main Form from a diffrent thread,
same messages occured.

Am I wrong? in window forms can I access component from other threads
they were not created in?

No, you can't. You can't add a new node to the tree from the non-UI
thread.

If using Control.Invoke is giving an error saying that you can't
access the TreeView because you're still on the wrong thread, that
suggests you're not quite using it correctly.

Could you post a short but complete program that demonstrates the
problem?

Jon
 
J

Jeroen

Falcon,

Like Jon said, some brief code would help us help you a lot better/
quicker.

That said, I encountered the same problems recently. Much to my
surprise a simple task such as updating a progressbar (or letting the
bar update itself) from another thread calls from some stunningly
complex code. Here's how I solved it:

/****************************/
// This code is on a form with a
// progressbar. The SetPercentage method
// is called from another thread.
internal void SetPercentage(int progress) {
if (this.ProgressBar.InvokeRequired)
{
SetPercentageDelegate callback = new
SetPercentageDelegate(SetPercentage);
this.Invoke(callback, new object[] { progress });
}
else if (progress >= 0 && progress <= 100)
this.ProgressBar.Value = progress;
}

internal delegate void SetPercentageDelegate(int progress);
/****************************/

Note however that I'm not in any way a guru yet, I'm still learning a
lot (so this might not be the 'correct' or 'simplest' solution).

Regards,
Jeroen
 
J

Jon Skeet [C# MVP]

Like Jon said, some brief code would help us help you a lot better/
quicker.

That said, I encountered the same problems recently. Much to my
surprise a simple task such as updating a progressbar (or letting the
bar update itself) from another thread calls from some stunningly
complex code. Here's how I solved it:

<snip>

The good news is that with simple enough feedback (like a progress
bar) BackgroundWorker makes life somewhat easier.

The other good news is that with .NET 3.5 there are various generic
delegate types which greatly reduce the need to create your own
ones :)

Jon
 
F

Falcon

WOW,
First thank you all for the nice quick responds.

Here is some code demonstrating what I'm doing:

I hava a main form with the following methods: (this is a bit pseudo)

private void button1_Click(object sender, EventArgs e)
{
prgDialog dlg = new prgDialog(veryLongAction);

if (dlg.ShowDialog() == DialogResult.OK)
{
MessageBox.Show("OK");
}
else
{
MessageBox.Show("Cancel");
}
}

public void veryLongAction(object sender, DoWorkEventArgs e)
{
for (_indexs = 0; _indexs < 10 * 1000000; _indexs++)
{
//File.AppendAllText(@"C:\progress.txt", _indexs + "
");

if (_indexs % 100 == 0)
{
//File.AppendAllText(@"C:\progress.txt",
Environment.NewLine);
}

// Check If thread is stil alive and cancel action if
its not.
}
}


On a diffrent form-dialog form (a ProgressBar Form) I have the
'Marquee' style progress bar and a cancel button.

public delegate string SomeAction();

public prgDialog()
{
InitializeComponent(SomeAction action);


backgroundWorker.WorkerSupportsCancellation = true;

backgroundWorker.DoWork += action;
backgroundWorker.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(actionCompleted);

backgroundWorker.RunWorkerAsync();
}

private void cancelButton_Click(object sender, EventArgs e)
{
backgroundWorker.CancelAsync();

this.DialogResult = DialogResult.Cancel;
this.Close();
}

private void actionCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
 
P

Peter Duniho

Falcon,

Like Jon said, some brief code would help us help you a lot better/
quicker.

That said, I encountered the same problems recently. Much to my
surprise a simple task such as updating a progressbar (or letting the
bar update itself) from another thread calls from some stunningly
complex code. Here's how I solved it:

/****************************/
// This code is on a form with a
// progressbar. The SetPercentage method
// is called from another thread.
internal void SetPercentage(int progress) {
if (this.ProgressBar.InvokeRequired)
{
SetPercentageDelegate callback = new
SetPercentageDelegate(SetPercentage);
this.Invoke(callback, new object[] { progress });
}
else if (progress >= 0 && progress <= 100)
this.ProgressBar.Value = progress;
}

internal delegate void SetPercentageDelegate(int progress);
/****************************/

Note however that I'm not in any way a guru yet, I'm still learning a
lot (so this might not be the 'correct' or 'simplest' solution).

I think you have a low threshold for being stunned. :)

The code you posted is pretty much the boilerplate given in MSDN for
dealing with these issues. I don't think it's all that complex. Which is
not to say that I find MSDN's advice perfect. :)

Personally, I find the check for "InvokeRequired" to be silly. It's not
harmful to use Invoke() when it's not needed (the performance overhead
should be minimal), and when you write code like the above it's almost
always because you're always calling the method from another thread anyway
(so "InvokeRequired" is always returning true the first time anyway).

So instead, I just assume that "InvokeRequired" is going to be true, and
write the code based on that. I also take advantage of anonymous methods,
so that the invoking can be done in the wrapper method, instead of
everywhere the update needs to be called from (which would be the other
alternative solution to MSDN's advice: just always use "Invoke()" to call
the "SetPercentage()" method wherever you use it, rather than making that
method worry about being on the right thread). An example follows:

void SetPercentage(int progress)
{
Invoke((MethodInvoker)delegate
{
if (progress >= 0 && progress <= 100)
{
ProgressBar.Value = progress;
}
});
}

I think that pattern is a lot simpler than MSDN's suggestion, and just as
effective.

Pete
 
J

Jeroen

I think you have a low threshold for being stunned. :)

Guess so :D. In any case, what I would have expected is some smart,
short keyword or something, like this:

// On non GUI thread, call GUI method:
SomeGuiObject.DoSomething() as AsyncGuiCall;

I guess that your pattern (assuming you need to invoke anyway) will
help me as well, making my code more readable (albeit at the expense
of some overhead).

The reason I was stunned I guess was because I don't see why all this
is even necessary: if the framework tells me "not allowed from non-GUI
thread" I just want it to use the damn GUI thread by default :)

Thanks for the explanations!

Regards,
Jeroen
 
P

Peter Duniho

Guess so :D. In any case, what I would have expected is some smart,
short keyword or something, like this:

// On non GUI thread, call GUI method:
SomeGuiObject.DoSomething() as AsyncGuiCall;

Well, I see the appeal of the syntax. Unfortunately, a) that would
require explicit support in the C# language (which otherwise doesn't
really "know" anything about the Forms namespace), and b) doesn't
translate well for situations where the method you want to call isn't
actually part of the GUI control class.

Besides, assuming you've already got a method that does the "something"
you want, is the above so much harder than writing:

SomeGuiObject.Invoke(SomeGuiObject.DoSomething);

?

Or in the specific example you had:

SomeGuiObject.Invoke(SomeGuiObject.SetPercentage, new object[]
{ progress } );

And all that assumes that the code isn't actually in the "SomeGuiObject"
class. If it was (and in your case it looks like it is), you could just
have this:

Invoke(SetPercentage, new object[] { progress } );

The parameter passing gets a _little_ messy, but otherwise it's pretty
straightforward. I only made the suggestion I did because I prefer for
the caller to not even have to worry about the cross-thread issue. But if
you're going to embed that information in the call itself, you might as
well use the one-line approaches above.
I guess that your pattern (assuming you need to invoke anyway) will
help me as well, making my code more readable (albeit at the expense
of some overhead).

Assuming you're using Invoke() in the usual way, there won't be _any_
overhead. It's only in the unusual situation where you are using the same
method for in-thread and cross-thread purposes that there'd be the
overhead, and I really believe the overhead will be minimal. You could
measure it if it's important to you, but given all the other stuff that
has to happen when you call a method that requires invoking in the first
place, the minimal work that the Invoke() method has to do in order to
deal with the in-thread case versus the cross-thread case isn't likely to
even show up on the radar.
The reason I was stunned I guess was because I don't see why all this
is even necessary: if the framework tells me "not allowed from non-GUI
thread" I just want it to use the damn GUI thread by default :)

Well, I think that's an especially legitimate question. In unmanaged
code, the rough equivalent would be the issues surrounding the use of
SendMessage(). I believe that the .NET requirement essentially inherits
from the same unmanaged Windows requirement that code using a window
class's window proc always execute on the thread that owns the window.
The requirement has to do with keeping access to the window data
structures thread-safe.

But in unmanaged code, if you call SendMessage() from the wrong thread,
Windows automatically marshals the call over to the right thread for you.
It basically does the Invoke()-like behavior on your behalf.

I'm not really clear on why .NET didn't take a similar approach. The
issue should only really come up for class to Control-derived class
methods that ultimately result in a call to SendMessage() and those should
get marshaled over to the right thread anyway.

I suspect that there is in fact a decent explanation, but it bugs me that
this explanation isn't immediately apparent. One thing that comes to mind
is that the concern was not that they couldn't make the thread change
automatic, but that doing so would lead to programmer confusion, as they
wonder "why the heck is this code executing on a completely different
thread?" for those cases where calling a Control-derived class method
winds up going through SendMessage() and then causing some client code to
then be executed (for example, setting the Text property, while subscribed
to a TextChanged event, assuming that the Control-derived class raises the
TextChanged event directly from the window proc when the window message
setting the text is sent).

But all the explanations I've seen don't discuss that possibility. They
all say that .NET _can't_ deal with the issue, rather than that it's just
a design decision to keep the coding simpler and safer.

Ah well...for now, one of the great mysteries of .NET (at least, to
me...if anyone's reading this and they know the answer, please post it!
And with great detail...no hand-waving, please :) ).

Pete
 
G

Gaz

Jeroen said:
Guess so :D. In any case, what I would have expected is some smart,
short keyword or something, like this:

// On non GUI thread, call GUI method:
SomeGuiObject.DoSomething() as AsyncGuiCall;

I guess that your pattern (assuming you need to invoke anyway) will
help me as well, making my code more readable (albeit at the expense
of some overhead).

The reason I was stunned I guess was because I don't see why all this
is even necessary: if the framework tells me "not allowed from non-GUI
thread" I just want it to use the damn GUI thread by default :)

Thanks for the explanations!

Regards,
Jeroen

You could use the AsyncOperation from the System.ComponentModel
namespace to do that.

Declare a private AsyncOperation in the class:

private AsyncOperation asyncOp;

And initialize it in the constructor with:

asyncOp = AsyncOperationManager.CreateOperation(null);

Now you can "invoke" any method in the context that asyncOp was
initialized, with:

asyncOp.Post(new SendOrPostCallback(method), parameter);

'method' is a method that takes an object and returns a void so you'll
have to cast the object back inside the method. If asyncOp was created
in the GUI thread then the method will be invoked in its message system.

Works like a charm.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top