Delegates, Threads, Invoke and other confusing stuff

M

mick

Understand all these on a *very* basic level but combine them and I`m at
a complete loss.

I was looking into how to access controls from another thread so went
looking
for an example and came across this but I`m struggling to follow what is
going
on. From http://www.osix.net/modules/article/?id=832 the odd label name has
been altered.

delegate void SetClockCallBack(string name);
//smart method to set labels`s text
public void SetClock(string name)
{
if(this.InvokeRequired)
{
SetClockCallBack callback = new SetClockCallBack(SetClock);
this.Invoke(callback,datetime);
}
else
{
this.lblClock.Text = datetime;
}

private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
Thread t = new Thread(new ThreadStart(ClockRun));
t.IsBackground = true;
t.Start();
}

private void ClockRun()
{
try
{
while (true)
{
// we'll just call the SetClock method here, no invoke
this.SetClock(DateTime.Now.ToString());
Thread.Sleep(1000);
}
}
catch (Exception ex)
{
// nothing is done here
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}

As I see it - in the button.Click event a new thread is created that will
run the ClockRun() method. This in turn calls SetClock() in which we
come to this -

if(this.InvokeRequired)
{
SetClockCallBack callback = new SetClockCallBack(SetClock);
this.Invoke(callback,datetime);
}

If you had to explain this to an idiot how would you do it? It looks to me
that SetClock is called again through Invoke via the delegate. A very
childlike
explanation or a better, smaller easier to understand example would help:)
 
P

Peter Duniho

mick said:
Understand all these on a *very* basic level but combine them and I`m at
a complete loss.

I don't think you are. You just think you are. :)
[...]
if(this.InvokeRequired)
{
SetClockCallBack callback = new SetClockCallBack(SetClock);
this.Invoke(callback,datetime);
}

If you had to explain this to an idiot how would you do it? It looks to me
that SetClock is called again through Invoke via the delegate.

That is exactly what is happening.
A very childlike
explanation or a better, smaller easier to understand example would help:)

I'm not sure any other example that actually does anything worth looking
at could be much simpler. However, the problem you are having
understanding the code is IMHO a good example of one of the reasons I
strongly dislike MSDN's prescription for dealing with cross-thread
invocations.

In particular, the thing that appears to be confusing you is the very
thing I really don't like about it: the method does two completely
different things, depending on the context in which it's executed.

The "if (this.InvokeRequired)" is the key. When the method is called
from a thread _other_ than the thread that owns the control, that
property is "true". Thus, the Control.Invoke() method is called with a
new delegate instance referring to, yes…you got that right, the method
currently executing!

This causes the thread calling that method to be blocked, while the call
to Invoke() completes. On the thread that owns the control, which is
not blocked (or at least, should not be :) ), the method is then called
again, as a result of the call to Invoke(). But, this time the
InvokeRequired property returns "false", and the "else" clause winds up
executing, setting the lblClock.Text property as desired.

Then the method returns, the UI thread (the one that owns the control)
continues on its merry way, and eventually the original thread is also
allowed to continue, as the Invoke() method finally returns.

As a general rule, any given method should do exactly one, simple thing.
And this is even true in this scenario.

For a more detailed rant on the subject, feel free to check out this
article I wrote:
http://msmvps.com/blogs/duniho/arch...chnique-for-using-control-invoke-is-lame.aspx

I never did find the time to write a lot for that blog, but that one
issue bugged me enough that I made sure it was on there. :) And who
knows, maybe I'll pick it up again some time.

Pete
 
M

mick

Peter Duniho said:
I don't think you are. You just think you are. :)

Aaarrgggh!!! Trust me I know I am:) Still cant follow it. I`m going to
have to search around for another example that clicks for me. Thanks
anyway.

mick
 
P

Peter Duniho

mick said:
Aaarrgggh!!! Trust me I know I am:) Still cant follow it.

What about my reply does not make sense to you?

From my point of view, or the point of view of anyone trying to explain
the concept, without knowing what you're having trouble with, it's
practically impossible to know how to construct an example that will help.

I'm happy to try to help, but I can't do it alone. I need your
cooperation, in the form of being specific about what you do and do not
understand, in order to tailor an explanation to fit your needs. The
same is true of anyone else who might be trying to help you as well.

Pete
 
M

mick

Peter Duniho said:
What about my reply does not make sense to you?

From my point of view, or the point of view of anyone trying to explain
the concept, without knowing what you're having trouble with, it's
practically impossible to know how to construct an example that will help.

I'm happy to try to help, but I can't do it alone. I need your
cooperation, in the form of being specific about what you do and do not
understand, in order to tailor an explanation to fit your needs. The same
is true of anyone else who might be trying to help you as well.

You sound as frustrated as I feel:) Ok, Ive taken a look at the Background
Worker. knocked up little app which is just a form, a button and a label.
When the user presses the button the label will move back and forth.

public partial class Form1 : Form
{
Point labelLocation;
public Form1()
{
InitializeComponent();
labelLocation = label1.Location;
}

private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.DoWork += new
DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged += new
ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerAsync();
}

void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e) //Called in main thread
{
label1.Location = (Point)e.UserState;
}

void backgroundWorker1_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e) //Called in main thread
{
// MessageBox.Show("Work Completed");
}

void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
//Called in new thread
{
int x = labelLocation.X;
int velocity = 2;
while (true)
{
labelLocation.X = x;
x += velocity;

if (x >= 100 || x<=0) //If limits reached change
direction
{
velocity = ~velocity;
velocity++;
}
Thread.Sleep(100);
backgroundWorker1.ReportProgress(velocity, labelLocation);
// Update the label Pos
}
}
}

Now using this I can see clearly what is going on and that the only method
called on the new thread is backgroundWorker1_DoWork().
Could you, if you have time, or someone else translate the above into the
Threads/Delegates/Invoke way of doing it to see if that switched the light
on upstairs.

Sorry for being a pain:)

mick
 
P

Peter Duniho

mick said:
[...]
I'm happy to try to help, but I can't do it alone. I need your
cooperation, in the form of being specific about what you do and do
not understand, in order to tailor an explanation to fit your needs.
The same is true of anyone else who might be trying to help you as well.

You sound as frustrated as I feel:)

Careful. You seem to be reading between the lines things that aren't
there. I'm not frustrated at all. I'm just telling you the facts.
Ok, Ive taken a look at the Background
Worker. knocked up little app which is just a form, a button and a label.
When the user presses the button the label will move back and forth.

Note that BackgroundWorker hides all of the cross-thread invocation
stuff from you.
[...]
Now using this I can see clearly what is going on and that the only method
called on the new thread is backgroundWorker1_DoWork().
Could you, if you have time, or someone else translate the above into the
Threads/Delegates/Invoke way of doing it to see if that switched the light
on upstairs.

I will, but as you'll see it's practically the same as the other example
you posted, as well as what you may have read on my blog article
(however, here I have avoided using anonymous methods and the generic
Action<T> delegate type…they are very useful IMHO and would actually
simplify the example below, but I don't want to add yet another new
concept here :) ).

Note that I left method names identical to those you originally chose,
to try to help you see the parallels between this version and the original.

Here's the code:

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(backgroundWorker1_DoWork);

thread.Start(label1.Location);
}

// Control.Invoke() requires a specific delegate type,
// so declare one compatible with the method we want to
// invoke:
private delegate void LocationUpdater(Point pt);

// Called in worker thread:
void _SetLabelLocation(Point pt)
{
label1.Invoke(
(LocationUpdater)backgroundWorker1_ProgressChanged,
new object[] { pt });
}

// Called in main thread, via Invoke():
void backgroundWorker1_ProgressChanged(Point pt)
{
label1.Location = pt;
}

// Entry point for worker thread
void backgroundWorker1_DoWork(object objLabelLocation)
{
Point labelLocation = (Point)objLabelLocation;

int x = labelLocation.X;
int velocity = 2;
while (true)
{
labelLocation.X = x;
x += velocity;

if (x >= 100 || x<=0) // If limits reached change direction
{
velocity = ~velocity;
velocity++;
}

Thread.Sleep(100);
_SetLabelLocation(labelLocation); // Update the label Pos
}
}
}
 
M

mick

Peter Duniho said:
Note that I left method names identical to those you originally chose, to
try to help you see the parallels between this version and the original.

Here's the code:

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(backgroundWorker1_DoWork);

thread.Start(label1.Location);
}

// Control.Invoke() requires a specific delegate type,
// so declare one compatible with the method we want to
// invoke:
private delegate void LocationUpdater(Point pt);

// Called in worker thread:
void _SetLabelLocation(Point pt)
{
label1.Invoke(
(LocationUpdater)backgroundWorker1_ProgressChanged,
new object[] { pt });
}

// Called in main thread, via Invoke():
void backgroundWorker1_ProgressChanged(Point pt)
{
label1.Location = pt;
}

// Entry point for worker thread
void backgroundWorker1_DoWork(object objLabelLocation)
{
Point labelLocation = (Point)objLabelLocation;

int x = labelLocation.X;
int velocity = 2;
while (true)
{
labelLocation.X = x;
x += velocity;

if (x >= 100 || x<=0) // If limits reached change direction
{
velocity = ~velocity;
velocity++;
}

Thread.Sleep(100);
_SetLabelLocation(labelLocation); // Update the label Pos
}
}
}

(I sent this reply hours ago but it didnt appear, so second try -)
(That didnt turn up either. Another day another try -)

Yep that did it. Just one thing though - at the end of
backgroundWorker1_DoWork() you call another method
(_SetLabelLocation) which itself invokes the updater. Is there
a reason not to put the Invoke just after the Thread.Sleep(100)
line? Is it a style thing/clarity thing or is there some deeper
reason that makes this a nono?

mick
 
M

mick

Peter Duniho said:
mick said:
[...]
I'm happy to try to help, but I can't do it alone. I need your
cooperation, in the form of being specific about what you do and do not
understand, in order to tailor an explanation to fit your needs. The
same is true of anyone else who might be trying to help you as well.

You sound as frustrated as I feel:)

Careful. You seem to be reading between the lines things that aren't
there. I'm not frustrated at all. I'm just telling you the facts.
Ok, Ive taken a look at the Background
Worker. knocked up little app which is just a form, a button and a label.
When the user presses the button the label will move back and forth.

Note that BackgroundWorker hides all of the cross-thread invocation stuff
from you.
[...]
Now using this I can see clearly what is going on and that the only
method
called on the new thread is backgroundWorker1_DoWork().
Could you, if you have time, or someone else translate the above into the
Threads/Delegates/Invoke way of doing it to see if that switched the
light
on upstairs.

I will, but as you'll see it's practically the same as the other example
you posted, as well as what you may have read on my blog article (however,
here I have avoided using anonymous methods and the generic Action<T>
delegate type…they are very useful IMHO and would actually simplify the
example below, but I don't want to add yet another new concept here :) ).

Note that I left method names identical to those you originally chose, to
try to help you see the parallels between this version and the original.

Here's the code:

public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(backgroundWorker1_DoWork);

thread.Start(label1.Location);
}

// Control.Invoke() requires a specific delegate type,
// so declare one compatible with the method we want to
// invoke:
private delegate void LocationUpdater(Point pt);

// Called in worker thread:
void _SetLabelLocation(Point pt)
{
label1.Invoke(
(LocationUpdater)backgroundWorker1_ProgressChanged,
new object[] { pt });
}

// Called in main thread, via Invoke():
void backgroundWorker1_ProgressChanged(Point pt)
{
label1.Location = pt;
}

// Entry point for worker thread
void backgroundWorker1_DoWork(object objLabelLocation)
{
Point labelLocation = (Point)objLabelLocation;

int x = labelLocation.X;
int velocity = 2;
while (true)
{
labelLocation.X = x;
x += velocity;

if (x >= 100 || x<=0) // If limits reached change direction
{
velocity = ~velocity;
velocity++;
}

Thread.Sleep(100);
_SetLabelLocation(labelLocation); // Update the label Pos
}
}
}



Ive made numerous replies to this in the last few days but none have got
through.
Next try -


Yep that did it. Just one thing though - at the end of
backgroundWorker1_DoWork() you call another method
(_SetLabelLocation) which itself invokes the updater. Is there
a reason not to put the Invoke just after the Thread.Sleep(100)
line? Is it a style thing/clarity thing or is there some deeper
reason that makes this a nono?

mick
 

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