HighTechTalks DotNet Forums  

lost in delegation

Dotnet Framework (CLR) microsoft.public.dotnet.framework.clr


Discuss lost in delegation in the Dotnet Framework (CLR) forum.



Reply
 
Thread Tools Search this Thread Display Modes
  #1  
Old   
Tim_Mac
 
Posts: n/a

Default lost in delegation - 06-19-2006 , 06:35 AM






hi,
i have encountered a strange behaviour when using ref parameters with
delegates. basically the changed value is not reflected back to the
original calling method.

to prove the point, run this simple console application. it sets a
string variable 's' with the current number of Ticks, then invokes a
method via a delegate, waits for it to finish, and outputs the
supposed-to-be-changed value. the value is changed in the method, but
is not reflected back.

using System;
using System.Threading;

namespace TestRef
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
new Class1();
}

public Class1()
{
string s = DateTime.Now.Ticks.ToString();
Console.WriteLine("Initialised: \t\t" + s);

// invoke a method using a delegate
new TestByRefDelegate(this.TestByRef).BeginInvoke(ref s, null,
null);
Thread.Sleep(500); // wait until the other thread is finished
Console.WriteLine("After delegate: \t" + s);

Console.Read();
}

public delegate void TestByRefDelegate(ref string s);
public void TestByRef(ref string s)
{
Console.WriteLine("Start of delegate: \t" + s);
s = DateTime.Now.Ticks.ToString();
Console.WriteLine("End of delegate: \t" + s);
}
}
}

can anyone shed some light on this? i just realised that it's of
course because the value is changed on the new thread, but that is
disposed of as soon as it finishes. this then begs the question, why
does the compiler allow delegates with ref parameters, since it is
impossible for the value to survive the lifetime of the thread?

thanks
tim


Reply With Quote
  #2  
Old   
Barry Kelly
 
Posts: n/a

Default Re: lost in delegation - 06-19-2006 , 07:55 AM






"Tim_Mac" <mackey.tim (AT) gmail (DOT) com> wrote:

Quote:
i have encountered a strange behaviour when using ref parameters with
delegates. basically the changed value is not reflected back to the
original calling method.

to prove the point, run this simple console application. it sets a
string variable 's' with the current number of Ticks, then invokes a
method via a delegate, waits for it to finish, and outputs the
supposed-to-be-changed value. the value is changed in the method, but
is not reflected back.
[snip]

// invoke a method using a delegate
new TestByRefDelegate(this.TestByRef).BeginInvoke(ref s, null,
null);
Thread.Sleep(500); // wait until the other thread is finished
Console.WriteLine("After delegate: \t" + s);

can anyone shed some light on this? i just realised that it's of
course because the value is changed on the new thread, but that is
disposed of as soon as it finishes. this then begs the question, why
does the compiler allow delegates with ref parameters, since it is
impossible for the value to survive the lifetime of the thread?
Calling EndInvoke is not optional. It's never optional: you must always
call EndInvoke. It may acquire resources (such as synchronization
objects) which need to be released on the call to EndInvoke.

The value doesn't get returned until you call EndInvoke, because
otherwise the background thread would have a reference to a variable
allocated on the stack, and that's not allowed because the method might
return without ever calling EndInvoke(), which would allow the
threadpool thread executing the delegate to write to whatever random
stack frame that has replaced the calling frame.

Check this out (C# 2.0):

---8<---
using System;
using System.Threading;

class App
{
delegate void TwiddleString(ref string value);

static void Main()
{
// With End* call.
TwiddleString twiddle = delegate(ref string value)
{
Thread.Sleep(500);
value = "bar!";
};

string foo = "foo!";
IAsyncResult res = twiddle.BeginInvoke(ref foo, null, null);

Thread.Sleep(700);
Console.WriteLine(foo);
twiddle.EndInvoke(ref foo, res);
Console.WriteLine(foo);
}
}
--->8---

-- Barry

--
http://barrkel.blogspot.com/


Reply With Quote
  #3  
Old   
Tim_Mac
 
Posts: n/a

Default Re: lost in delegation - 06-19-2006 , 11:19 AM



hi Barry,
many thanks for your enlightening post. i've been using Delegates for
a good long time never using EndInvoke, typically raising an event from
the delegate to signify the task completion. but this obviously isn't
the whole story!
thanks.
tim

Barry Kelly wrote:
Quote:
"Tim_Mac" <mackey.tim (AT) gmail (DOT) com> wrote:

i have encountered a strange behaviour when using ref parameters with
delegates. basically the changed value is not reflected back to the
original calling method.

to prove the point, run this simple console application. it sets a
string variable 's' with the current number of Ticks, then invokes a
method via a delegate, waits for it to finish, and outputs the
supposed-to-be-changed value. the value is changed in the method, but
is not reflected back.
[snip]

// invoke a method using a delegate
new TestByRefDelegate(this.TestByRef).BeginInvoke(ref s, null,
null);
Thread.Sleep(500); // wait until the other thread is finished
Console.WriteLine("After delegate: \t" + s);

can anyone shed some light on this? i just realised that it's of
course because the value is changed on the new thread, but that is
disposed of as soon as it finishes. this then begs the question, why
does the compiler allow delegates with ref parameters, since it is
impossible for the value to survive the lifetime of the thread?

Calling EndInvoke is not optional. It's never optional: you must always
call EndInvoke. It may acquire resources (such as synchronization
objects) which need to be released on the call to EndInvoke.

The value doesn't get returned until you call EndInvoke, because
otherwise the background thread would have a reference to a variable
allocated on the stack, and that's not allowed because the method might
return without ever calling EndInvoke(), which would allow the
threadpool thread executing the delegate to write to whatever random
stack frame that has replaced the calling frame.

Check this out (C# 2.0):

---8<---
using System;
using System.Threading;

class App
{
delegate void TwiddleString(ref string value);

static void Main()
{
// With End* call.
TwiddleString twiddle = delegate(ref string value)
{
Thread.Sleep(500);
value = "bar!";
};

string foo = "foo!";
IAsyncResult res = twiddle.BeginInvoke(ref foo, null, null);

Thread.Sleep(700);
Console.WriteLine(foo);
twiddle.EndInvoke(ref foo, res);
Console.WriteLine(foo);
}
}
--->8---

-- Barry

--
http://barrkel.blogspot.com/


Reply With Quote
  #4  
Old   
Barry Kelly
 
Posts: n/a

Default Re: lost in delegation - 06-19-2006 , 01:16 PM



"Tim_Mac" <mackey.tim (AT) gmail (DOT) com> wrote:

Quote:
many thanks for your enlightening post. i've been using Delegates for
a good long time never using EndInvoke, typically raising an event from
the delegate to signify the task completion. but this obviously isn't
the whole story!
You're welcome. In C# 2.0, I've got a penchant for passing an anonymous
delegate argument to ThreadPool.QueueUserWorkItem() to get the same
effect (run something in the background), without needing to keep track
of an IAsyncResult. General pattern:

ThreadPool.QueueUserWorkItem(delegate
{
// do stuff
});

Of course, you've got to be careful of exceptions thrown. So, I actually
have library routines that run a delegate in the background, but wrap
the execution in an appropriate exception handler.

-- Barry

--
http://barrkel.blogspot.com/


Reply With Quote
  #5  
Old   
Jon Skeet [C# MVP]
 
Posts: n/a

Default Re: lost in delegation - 06-19-2006 , 03:10 PM



Barry Kelly <barry.j.kelly (AT) gmail (DOT) com> wrote:
Quote:
can anyone shed some light on this? i just realised that it's of
course because the value is changed on the new thread, but that is
disposed of as soon as it finishes. this then begs the question, why
does the compiler allow delegates with ref parameters, since it is
impossible for the value to survive the lifetime of the thread?

Calling EndInvoke is not optional. It's never optional: you must always
call EndInvoke. It may acquire resources (such as synchronization
objects) which need to be released on the call to EndInvoke.
Small exception to that - for Control.BeginInvoke, you don't need to
call EndInvoke. See
http://blogs.msdn.com/cbrumme/archiv.../06/51385.aspx

--
Jon Skeet - <skeet (AT) pobox (DOT) com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too


Reply With Quote
  #6  
Old   
Barry Kelly
 
Posts: n/a

Default Re: lost in delegation - 06-19-2006 , 04:40 PM



Jon Skeet [C# MVP] <skeet (AT) pobox (DOT) com> wrote:

Quote:
Barry Kelly <barry.j.kelly (AT) gmail (DOT) com> wrote:
can anyone shed some light on this? i just realised that it's of
course because the value is changed on the new thread, but that is
disposed of as soon as it finishes. this then begs the question, why
does the compiler allow delegates with ref parameters, since it is
impossible for the value to survive the lifetime of the thread?

Calling EndInvoke is not optional. It's never optional: you must always
call EndInvoke. It may acquire resources (such as synchronization
objects) which need to be released on the call to EndInvoke.

Small exception to that - for Control.BeginInvoke, you don't need to
call EndInvoke. See
http://blogs.msdn.com/cbrumme/archiv.../06/51385.aspx
It's in the comment section:

CB> Last night I sent an email to the WinForms folks asking this same
CB> question. Like you, I suspect the EndInvoke is optional on Control.
CB> (I looked through the code, but that's no substitute for a statement
CB> from the authors). As you say, this API doesn't quite match the
CB> managed async programming model anyway. I'll reply back as soon as I
CB> get official word.

It's not exactly definitive! I like to stick with always matching with
the End* method, to be safe. Normally, I rather use
ThreadPool.QueueUserWorkItem() where I can (like I mentioned in the
other message). For async methods where they're needed (such as server
IO to avoid lots of redundant blocked threads), well, I try to avoid
that until it's demonstrably necessary.

-- Barry

--
http://barrkel.blogspot.com/


Reply With Quote
  #7  
Old   
Jon Skeet [C# MVP]
 
Posts: n/a

Default Re: lost in delegation - 06-22-2006 , 06:37 PM



Barry Kelly <barry.j.kelly (AT) gmail (DOT) com> wrote:
Quote:
Small exception to that - for Control.BeginInvoke, you don't need to
call EndInvoke. See
http://blogs.msdn.com/cbrumme/archiv.../06/51385.aspx

It's in the comment section:

CB> Last night I sent an email to the WinForms folks asking this same
CB> question. Like you, I suspect the EndInvoke is optional on Control.
CB> (I looked through the code, but that's no substitute for a statement
CB> from the authors). As you say, this API doesn't quite match the
CB> managed async programming model anyway. I'll reply back as soon as I
CB> get official word.
You missed the bit later on - Chris Brumme again:

<quote>
Chris Brumme said:
I just got the official word from the WinForms team. It is not
necessary to call Control.EndInvoke. You can call BeginInvoke in a
"fire and forget" manner with impunity.
May 12, 2003 5:30 PM
</quote>

I know it's on a blog rather than being part of the documentation, but
I think it's still reasonably definitive. Let's say I trust Chris
Brumme when it comes to this kind of thing

--
Jon Skeet - <skeet (AT) pobox (DOT) com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too


Reply With Quote
  #8  
Old   
Jon Shemitz
 
Posts: n/a

Default Re: lost in delegation - 06-22-2006 , 07:20 PM



"Jon Skeet [C# MVP]" wrote:

Quote:
I know it's on a blog rather than being part of the documentation, but
I think it's still reasonably definitive. Let's say I trust Chris
Brumme when it comes to this kind of thing
I do, too. The blog certainly influenced my explanation of
Control.Invoke behavior.

--
Thank you for reading this fine message from the author of

..NET 2.0 for Delphi Programmers www.midnightbeach.com/.net
Delphi skills make .NET easy to learn In print, in stores.


Reply With Quote
  #9  
Old   
Barry Kelly
 
Posts: n/a

Default Re: lost in delegation - 06-22-2006 , 07:40 PM



Jon Skeet [C# MVP] <skeet (AT) pobox (DOT) com> wrote:

Quote:
You missed the bit later on - Chris Brumme again:

quote
Chris Brumme said:
I just got the official word from the WinForms team. It is not
necessary to call Control.EndInvoke. You can call BeginInvoke in a
"fire and forget" manner with impunity.
May 12, 2003 5:30 PM
/quote
I did miss that, yes. This prompted me to open up Reflector and peek a
little further - and I was disappointed by what I found.

The upshot is that it's possible that a ManualResetEvent is leaked and
not disposed every time you (i) call BeginInvoke() and (ii) access the
AsyncWaitHandle on the returned IAsyncResult.

Quote:
I know it's on a blog rather than being part of the documentation, but
I think it's still reasonably definitive. Let's say I trust Chris
Brumme when it comes to this kind of thing
I have less trust in the WinForms team themselves. They have
mis-implemented the Dispose/Finalize pattern on the
Control.ThreadMethodEntry class. An instance of this class is returned
when you call BeginInvoke().

The situation: a ManualResetEvent is allocated if you ever get the value
of the AsyncWaitHandle property (this is lazy allocation, and is to be
expected and is good). This ManualResetEvent internally holds a
SafeHandle which wraps the real OS-level object, and is, of course, a
critical finalizable object. IMO, that doesn't excuse the developer from
disposing of it whenever they reasonably can. GC is for memory not
resources etc etc.

The problems:

1) The Control.ThreadMethodEntry class has no method which disposes of
this contained ManualResetEvent, so even if one calls
Control.EndInvoke(), a critical resource will be leaked and the GC
finalizer thread is relied upon to collect it.

2) The Control.ThreadMethodEntry class implements a pointless finalizer
which calls Close on the internal ManualResetEvent if it exists. This
indicates a misunderstanding of GC and finalization: finalization
routines shouldn't access any references they contain to other managed
classes, because they may have been collected and finalized themselves.
This is the whole purpose behind the boolean argument to the virtual
protected Dispose(bool) method in the Dispose pattern.

In effect, the folks in the WinForms team implemented a Finalizer on
Control.ThreadMethodEntry instead of IDispose. They should have
implemented IDispose and called Dispose() from EndInvoke(), and stayed
well away from Finalize(), which is very rarely needed.

-- Barry

--
http://barrkel.blogspot.com/


Reply With Quote
  #10  
Old   
Peter Bromley
 
Posts: n/a

Default Re: lost in delegation - 06-25-2006 , 09:47 PM



Jon Skeet [C# MVP] wrote:
Quote:
Barry Kelly <barry.j.kelly (AT) gmail (DOT) com> wrote:

Small exception to that - for Control.BeginInvoke, you don't need to
call EndInvoke. See
http://blogs.msdn.com/cbrumme/archiv.../06/51385.aspx

It's in the comment section:

CB> Last night I sent an email to the WinForms folks asking this same
CB> question. Like you, I suspect the EndInvoke is optional on Control.
CB> (I looked through the code, but that's no substitute for a statement
CB> from the authors). As you say, this API doesn't quite match the
CB> managed async programming model anyway. I'll reply back as soon as I
CB> get official word.


You missed the bit later on - Chris Brumme again:

quote
Chris Brumme said:
I just got the official word from the WinForms team. It is not
necessary to call Control.EndInvoke. You can call BeginInvoke in a
"fire and forget" manner with impunity.
May 12, 2003 5:30 PM
/quote

I know it's on a blog rather than being part of the documentation, but
I think it's still reasonably definitive. Let's say I trust Chris
Brumme when it comes to this kind of thing

One nit.

If an exception is thrown within your delegate, you will only get the
exception throw to you if you call EndInvoke

--

Peter Bromley


Reply With Quote
Reply




Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off



Powered by vBulletin Version 3.5.4
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.