|
|
|
date: Tue, 8 Jul 2008 10:25:37 -0500,
group: microsoft.public.dotnet.languages.csharp
back
Control.BeginInvoke is NOT fire-and-forget
As much as the CLR team assures us that it's ok to fire-and-forget
Control.BeginInvoke, it seems it isn't. Maybe this is a bug.
See for example: the comments in
http://blogs.msdn.com/cbrumme/archive/2003/05/06/51385.aspx
I was encountering a bug that disappeared when debugging. Not when a
debugger is attached, mind you, but when I placed a breakpoint near the
code. Adding Trace.WriteLine statements showed that the failing code was
not even being executed. Ok, what effects can a breakpoint have? Well,
evaluation of watch expressions, so I cleared the watch window... check,
same behavior. And occasionally (<10%) the code worked even with the
breakpoint removed or disabled. Must be a race condition, hitting a
breakpoint could definitely affect thread scheduling.
Turns out this code (now fixed) was the culprit:
if (postProcessing != null)
{
new UIPermission(UIPermissionWindow.AllWindows).Assert();
Control c = new Control();
IntPtr forceHandleCreation = c.Handle;
MethodInvoker finalProcessing = postProcessing + c.Dispose;
helper.postProcessing = delegate {
c.Invoke(finalProcessing); };
}
new System.Threading.Thread(helper.UIThreadProc).Start();
The thread procedure:
public void UIThreadProc()
{
new UIPermission(UIPermissionWindow.AllWindows).Assert();
progressDialog = new
ProgressTracker((ushort)fileArray.Length, cumulativeSize, actionMsg);
IntPtr forceHandleCreation = progressDialog.Handle;
new System.Threading.Thread(WorkThreadProc).Start();
Application.Run(progressDialog);
if (postProcessing != null)
postProcessing();
}
I originally had c.BeginInvoke in the asynchronous method. Seems that if
you BeginInvoke and then the calling thread ends, the call never takes
place. Yuck!
Do you think this is a CLR bug or it is by design?
date: Tue, 8 Jul 2008 10:25:37 -0500
author: Ben Voigt [C++ MVP] am
RE: Control.BeginInvoke is NOT fire-and-forget
I can understand why this might happen but I wouldnt think it would be by
design.
If it is by design then I guess they expected you to call EndInvoke after
doing more work or something.
--
Ciaran O''Donnell
http://wannabedeveloper.spaces.live.com
"Ben Voigt [C++ MVP]" wrote:
> As much as the CLR team assures us that it's ok to fire-and-forget
> Control.BeginInvoke, it seems it isn't. Maybe this is a bug.
>
> See for example: the comments in
> http://blogs.msdn.com/cbrumme/archive/2003/05/06/51385.aspx
>
> I was encountering a bug that disappeared when debugging. Not when a
> debugger is attached, mind you, but when I placed a breakpoint near the
> code. Adding Trace.WriteLine statements showed that the failing code was
> not even being executed. Ok, what effects can a breakpoint have? Well,
> evaluation of watch expressions, so I cleared the watch window... check,
> same behavior. And occasionally (<10%) the code worked even with the
> breakpoint removed or disabled. Must be a race condition, hitting a
> breakpoint could definitely affect thread scheduling.
>
> Turns out this code (now fixed) was the culprit:
>
> if (postProcessing != null)
> {
> new UIPermission(UIPermissionWindow.AllWindows).Assert();
> Control c = new Control();
> IntPtr forceHandleCreation = c.Handle;
> MethodInvoker finalProcessing = postProcessing + c.Dispose;
> helper.postProcessing = delegate {
> c.Invoke(finalProcessing); };
> }
>
> new System.Threading.Thread(helper.UIThreadProc).Start();
>
> The thread procedure:
>
> public void UIThreadProc()
> {
> new UIPermission(UIPermissionWindow.AllWindows).Assert();
> progressDialog = new
> ProgressTracker((ushort)fileArray.Length, cumulativeSize, actionMsg);
> IntPtr forceHandleCreation = progressDialog.Handle;
> new System.Threading.Thread(WorkThreadProc).Start();
> Application.Run(progressDialog);
> if (postProcessing != null)
> postProcessing();
> }
>
> I originally had c.BeginInvoke in the asynchronous method. Seems that if
> you BeginInvoke and then the calling thread ends, the call never takes
> place. Yuck!
>
> Do you think this is a CLR bug or it is by design?
>
>
>
date: Tue, 8 Jul 2008 09:07:02 -0700
author: Ciaran O''Donnell
Re: Control.BeginInvoke is NOT fire-and-forget
On Tue, 08 Jul 2008 08:25:37 -0700, Ben Voigt [C++ MVP]
<rbv@nospam.nospam> wrote:
> As much as the CLR team assures us that it's ok to fire-and-forget
> Control.BeginInvoke, it seems it isn't. Maybe this is a bug.
>
> See for example: the comments in
> http://blogs.msdn.com/cbrumme/archive/2003/05/06/51385.aspx
>
> [...]
> Do you think this is a CLR bug or it is by design?
First thing to keep in mind: the assertion about Control.EndInvoke() has
to do with resource cleanup and whether one is required to call that
method to ensure things are cleaned up. It's not about whether
Control.BeginInvoke() will work.
Second, it would be helpful if you'd actually post a concise-but-complete
code sample that reliably demonstrates the problem. Saying "I original
had c.BeginInvoke in the asynchronous method" doesn't tell us much about
how you actually used it or what might have been going wrong.
I am relatively confident that if you call BeginInvoke() from a thread
that exits before the invoked delegate gets to run, the invoked delegate
should still run. I would be very surprised if that wasn't actually what
happened. On the other hand, if the thread that _owns_ the control being
used to call BeginInvoke() exits or is otherwise terminated, I would _not_
expect the delegate being invoked to execute, since it has to execute on
that thread.
Again, a complete code sample would eliminate these ambiguities in your
comment. It's impossible to tell for sure from the code you posted what
exactly you were trying to do and what broke. It also doesn't help that
the code you posted is clearly a corner case, whatever else might have
been going on, and you didn't post enough to show us that you've correctly
set the threading model for whatever threads wind up with a message pump
(something that could also break things).
Given the evidence so far, I cannot imaging being able to confidently say
there's a bug, whether in the CLR or (as is probably more likely, assuming
this is a bug at all) in the framework.
Pete
date: Tue, 08 Jul 2008 09:53:28 -0700
author: Peter Duniho
RE: Control.BeginInvoke is NOT fire-and-forget
Thanks Pete and Ciaran for your help!
Hi Ben,
The Control.BeginInvoke method executes the specified delegate
asynchronously on the thread that the control's underlying handle was
created on. It means that the calling thread doen't need to wait until the
UI thread finishes processing the request and will returns immediately.
It's true that you should always call a delegate's EndInvoke after a call
to a delegate's BeginInvoke. It's completely safe to call
Control.BeginInvoke without ever calling Control.EndInvoke, because it
doesn't create the same resources associated with a delegate's BeginInvoke
call.
Even if you do want the results from a call to Control.BeginInvoke, there's
no way to pass a callback, so you need to use the IAsyncResult
implementation as returned from Control.BeginInvoke. You keep checking the
IsCompleted property for true during your other worker thread processing
before calling Control.EndInvoke to harvest the result. This is such a pain
that, if you want results from the call to the UI thread, I suggest that
the worker thread use Control.Invoke instead.
> As much as the CLR team assures us that it's ok to fire-and-forget
Control.BeginInvoke, it seems it isn't.
Could you please show us a complete code snippet to demonstrate the problem?
I look forward to your reply!
Sincerely,
Linda Liu
Microsoft Online Community Support
Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided. You can send feedback directly to my manager at:
msdnmg@microsoft.com.
==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.
Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
date: Wed, 09 Jul 2008 07:20:05 GMT
author: (Linda Liu[MSFT])
Re: Control.BeginInvoke is NOT fire-and-forget
Peter Duniho wrote:
> On Tue, 08 Jul 2008 08:25:37 -0700, Ben Voigt [C++ MVP]
> <rbv@nospam.nospam> wrote:
>
>> As much as the CLR team assures us that it's ok to fire-and-forget
>> Control.BeginInvoke, it seems it isn't. Maybe this is a bug.
>>
>> See for example: the comments in
>> http://blogs.msdn.com/cbrumme/archive/2003/05/06/51385.aspx
>>
>> [...]
>> Do you think this is a CLR bug or it is by design?
>
> First thing to keep in mind: the assertion about Control.EndInvoke()
> has to do with resource cleanup and whether one is required to call
> that method to ensure things are cleaned up. It's not about whether
> Control.BeginInvoke() will work.
>
> Second, it would be helpful if you'd actually post a
> concise-but-complete code sample that reliably demonstrates the
> problem. Saying "I original had c.BeginInvoke in the asynchronous
> method" doesn't tell us much about how you actually used it or what
> might have been going wrong.
Did I write that? Sure enough.
I should have said "in the anonymous method".
i.e. changing the line from
helper.postProcessing = delegate { c.Invoke(finalProcessing); };
back to
helper.postProcessing = delegate { c.BeginInvoke(finalProcessing); };
breaks things, in that the finalProcessing MulticastDelegate never runs nor
throws an exception.
>
> I am relatively confident that if you call BeginInvoke() from a thread
> that exits before the invoked delegate gets to run, the invoked
> delegate should still run. I would be very surprised if that wasn't
> actually what happened. On the other hand, if the thread that _owns_
> the control being used to call BeginInvoke() exits or is otherwise
> terminated, I would _not_ expect the delegate being invoked to
> execute, since it has to execute on that thread.
The control should be owned by the original thread which does not exit, I
read the Handle property for the explicit purpose of forcing it to be
created on that thread, before I spawn the worker.
>
> Again, a complete code sample would eliminate these ambiguities in
> your comment. It's impossible to tell for sure from the code you
> posted what exactly you were trying to do and what broke. It also
> doesn't help that the code you posted is clearly a corner case,
> whatever else might have been going on, and you didn't post enough to
> show us that you've correctly set the threading model for whatever
> threads wind up with a message pump (something that could also break
> things).
Changing the call in the asynchronous method which is called at the very end
of the worker threadproc from BeginInvoke to Invoke does cure the problem.
I think this demonstrates that the thread on which the control is created is
properly pumping messages.
>
> Given the evidence so far, I cannot imaging being able to confidently
> say there's a bug, whether in the CLR or (as is probably more likely,
> assuming this is a bug at all) in the framework.
True, it's most likely a bug in the base class libraries, not the CLR. My
poor wording.
>
> Pete
date: Wed, 9 Jul 2008 08:41:10 -0500
author: Ben Voigt [C++ MVP] am
Re: Control.BeginInvoke is NOT fire-and-forget
On Wed, 09 Jul 2008 06:41:10 -0700, Ben Voigt [C++ MVP]
<rbv@nospam.nospam> wrote:
> [...]
>> Again, a complete code sample would eliminate these ambiguities in
>> your comment. It's impossible to tell for sure from the code you
>> posted what exactly you were trying to do and what broke. It also
>> doesn't help that the code you posted is clearly a corner case,
>> whatever else might have been going on, and you didn't post enough to
>> show us that you've correctly set the threading model for whatever
>> threads wind up with a message pump (something that could also break
>> things).
>
> Changing the call in the asynchronous method which is called at the very
> end
> of the worker threadproc from BeginInvoke to Invoke does cure the
> problem.
> I think this demonstrates that the thread on which the control is
> created is
> properly pumping messages.
Given the code that was posted, at best it demonstrates that using
Invoke() treats the call differently than using BeginInvoke(). For
example, it could be that there's some logic in the Invoke() method that
winds up calling the delegate on the current thread.
You have all the code, so you can of course make your own judgments. But
absent a concise-but-complete code sample, I'm leery of making assumptions
about what is specifically happening.
The glimpse of the design that we've seen so far makes me think that
there's probably a much better way to approach whatever you're doing
anyway. Creating a dummy Control instance simply for the purpose of
marshalling execution back to some thread seems very odd to me. At the
very least, I'd think a SynchronizationContext would be more appropriate,
since it wouldn't carry all the extra unused baggage a Control has. And
usually, when you actually need code to be marshalled back to a specific
GUI thread, it's because you have a specific Control instance that
requires it, and you can just use that instance to do the marshalling (in
that case, the logic would be moved to whatever code is in your
"postProcessing" delegate...again, this seems more sensible to me than
requiring some other code to manage the marshalling arbitrarily).
But, all that said, even if we take as granted that the design is the very
best approach for managing this, no one here can answer the question as to
whether there's a bug in the runtime or not. We don't have all the code,
and so there's no way to know for sure that it's not just a bug in your
code.
Personally, I can count on the fingers of one hand with at least one
finger left over the number of times that someone has said "this is a .NET
bug" and they wound up being right. That's in spite of seeing such
statements many dozens of times. Granted, I give you a much higher chance
of being correct on such a statement than the average schmoe, but I'm
still hesitant to just take the assertion at face value. I'd want to see
the code.
But, as long as all you want is for your code to work, I think you've got
your solution. Based only on what you've posted, no one can say whether
it's a .NET bug, but it doesn't sound like that's an important
consideration for you anyway.
Pete
date: Wed, 09 Jul 2008 09:42:50 -0700
author: Peter Duniho
Re: Control.BeginInvoke is NOT fire-and-forget
> The glimpse of the design that we've seen so far makes me think that
> there's probably a much better way to approach whatever you're doing
> anyway. Creating a dummy Control instance simply for the purpose of
> marshalling execution back to some thread seems very odd to me. At
> the very least, I'd think a SynchronizationContext would be more
> appropriate, since it wouldn't carry all the extra unused baggage a
> Control has. And usually, when you actually need code to be
> marshalled back to a specific GUI thread, it's because you have a
> specific Control instance that requires it, and you can just use that
> instance to do the marshalling (in that case, the logic would be
> moved to whatever code is in your "postProcessing" delegate...again,
> this seems more sensible to me than requiring some other code to
> manage the marshalling arbitrarily).
Hmmm. In Win32, creating a message-only window is a pretty common paradigm.
And I'm not seeing how SynchronizationContext can help me, at a minimum it
looks an order of magnitude more complicated to use than Control.Invoke or
Control.BeginInvoke. As far as "extra baggage" of a Control, isn't
cross-thread marshalling dependent on window messages anyway (hence the
concern for the message loop of the receiving thread), or has .NET conflated
async calls with the UI message loop? It sure isn't using the Win32 APC
mechanism, which would solve the problem nicely with no extra Control needed
if only the main WinForms message loop did an alertable wait... but it
doesn't. I filed a feature request on that over a year ago.
As for why, I'm trying to have my components present a single-threaded event
driven interface. Components implemented event-driven with a state machine,
can fire events directly from their event handlers. Components that need
background threads, the thread synchronization code is hidden in the
implementation where the consumer doesn't ever have to know extra threads
were created.
date: Wed, 9 Jul 2008 17:30:56 -0500
author: Ben Voigt [C++ MVP] am
Re: Control.BeginInvoke is NOT fire-and-forget
On Wed, 09 Jul 2008 15:30:56 -0700, Ben Voigt [C++ MVP]
<rbv@nospam.nospam> wrote:
> Hmmm. In Win32, creating a message-only window is a pretty common
> paradigm.
That's true. But AFAIK, a Control instance isn't a message-only window.
> And I'm not seeing how SynchronizationContext can help me, at a minimum
> it
> looks an order of magnitude more complicated to use than Control.Invoke
> or
> Control.BeginInvoke.
Why? "SynchronizationContext.Current.Post( /* your delegate here */ );"
seems simple enough. For the purpose of your anonymous method, you'll
probably want to capture the "Current" context for use later.
What about it do you feel is an order of magnitude more complicated?
Seems to me that creating a whole new Control instance and forcing the
handle to be created is twice as many lines of code. :)
> As far as "extra baggage" of a Control, isn't
> cross-thread marshalling dependent on window messages anyway (hence the
> concern for the message loop of the receiving thread), or has .NET
> conflated
> async calls with the UI message loop?
That I don't know. The point is that the SynchronizationContext instance
already exists, so you might as well use it rather than creating a whole
new Control instance. :)
> It sure isn't using the Win32 APC
> mechanism, which would solve the problem nicely with no extra Control
> needed
> if only the main WinForms message loop did an alertable wait... but it
> doesn't. I filed a feature request on that over a year ago.
Yes, there are still some things in the unmanaged API that would be nice
to have in .NET.
> As for why, I'm trying to have my components present a single-threaded
> event
> driven interface. Components implemented event-driven with a state
> machine,
> can fire events directly from their event handlers. Components that need
> background threads, the thread synchronization code is hidden in the
> implementation where the consumer doesn't ever have to know extra threads
> were created.
Personally, I don't mind a design that requires the consumer to know that
an event or other callback might happen on a different thread. .NET
certainly has plenty of examples of that, and they seem to work okay.
Depending on how you expect the thing to be used, you _could_ look at the
Target for the delegate being invoked to see if it implements
ISynchronizeInvoke, and then use the Invoke/BeginInvoke on that target for
the callback. This of course requires that the delegate be a member of
the Control sub-class in situations when it must be called on the GUI
thread. Obviously there's no way to guarantee that.
Barring that, in this particular situation perhaps SynchronizationContext
would work for you. Assuming, of course, I haven't missed something and
it's not really an order of magnitude more complicated. I certainly would
agree that if it takes ten times the code, it might be better to do it
some other way. :)
Pete
date: Wed, 09 Jul 2008 22:17:29 -0700
author: Peter Duniho
Re: Control.BeginInvoke is NOT fire-and-forget
Peter Duniho wrote:
> On Wed, 09 Jul 2008 15:30:56 -0700, Ben Voigt [C++ MVP]
> <rbv@nospam.nospam> wrote:
>
>> Hmmm. In Win32, creating a message-only window is a pretty common
>> paradigm.
>
> That's true. But AFAIK, a Control instance isn't a message-only
> window.
>> And I'm not seeing how SynchronizationContext can help me, at a
>> minimum it
>> looks an order of magnitude more complicated to use than
>> Control.Invoke or
>> Control.BeginInvoke.
>
> Why? "SynchronizationContext.Current.Post( /* your delegate here */
> );" seems simple enough. For the purpose of your anonymous method,
> you'll probably want to capture the "Current" context for use later.
Ok, that's not too bad. An extra anonymous method is required in order to
make a SendOrPostCallback delegate. And you must save the Current context.
So about equal.
I think I just didn't see any example of how to use it. The documentation
for that class is beyond horrible. It starts out by saying "The
SynchronizationContext class is a base class that provides a free-threaded
context with no synchronization." The IsWaitNotificationRequired method
(shouldn't it be a property?) -- well the method name is more informative
than the entire documentation page. And so on, with not a single example
linked from any of the class members.
>
> What about it do you feel is an order of magnitude more complicated?
> Seems to me that creating a whole new Control instance and forcing the
> handle to be created is twice as many lines of code. :)
>
>> As far as "extra baggage" of a Control, isn't
>> cross-thread marshalling dependent on window messages anyway (hence
>> the concern for the message loop of the receiving thread), or has
>> .NET conflated
>> async calls with the UI message loop?
>
> That I don't know. The point is that the SynchronizationContext
> instance already exists, so you might as well use it rather than
> creating a whole new Control instance. :)
>
>> It sure isn't using the Win32 APC
>> mechanism, which would solve the problem nicely with no extra Control
>> needed
>> if only the main WinForms message loop did an alertable wait... but
>> it doesn't. I filed a feature request on that over a year ago.
>
> Yes, there are still some things in the unmanaged API that would be
> nice to have in .NET.
>
>> As for why, I'm trying to have my components present a
>> single-threaded event
>> driven interface. Components implemented event-driven with a state
>> machine,
>> can fire events directly from their event handlers. Components that
>> need background threads, the thread synchronization code is hidden
>> in the implementation where the consumer doesn't ever have to know
>> extra threads were created.
>
> Personally, I don't mind a design that requires the consumer to know
> that an event or other callback might happen on a different thread. .NET
> certainly has plenty of examples of that, and they seem to work
> okay.
> Depending on how you expect the thing to be used, you _could_ look at
> the Target for the delegate being invoked to see if it implements
> ISynchronizeInvoke, and then use the Invoke/BeginInvoke on that
> target for the callback. This of course requires that the delegate
> be a member of the Control sub-class in situations when it must be
> called on the GUI thread. Obviously there's no way to guarantee that.
>
> Barring that, in this particular situation perhaps
> SynchronizationContext would work for you. Assuming, of course, I
> haven't missed something and it's not really an order of magnitude
> more complicated. I certainly would agree that if it takes ten times
> the code, it might be better to do it some other way. :)
>
> Pete
date: Thu, 10 Jul 2008 09:00:04 -0500
author: Ben Voigt [C++ MVP] am
|
|