Control.BeginInvoke is NOT fire-and-forget

  • Thread starter Ben Voigt [C++ MVP]
  • Start date
B

Ben Voigt [C++ MVP]

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?
 
C

Ciaran O''Donnell

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.
 
L

Linda Liu[MSFT]

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:
(e-mail address removed).

==================================================
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.
 
B

Ben Voigt [C++ MVP]

Peter said:
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.
 
B

Ben Voigt [C++ MVP]

Peter said:
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.

I tried and it doesn't reproduce with an unloaded system. The original
project has a few other threads of varying priorities doing unrelated tasks
but certainly affecting thread scheduling and I can definitely understand
why that can affect the appearance of a race condition. I'll just use
Invoke, the thread is dying anyway, it can stay around long enough to get
the delegate call completed message.
 
B

Ben Voigt [C++ MVP]

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.
 
B

Ben Voigt [C++ MVP]

Peter said:
That's true. But AFAIK, a Control instance isn't a message-only
window.

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.
 
B

Ben Voigt [C++ MVP]

As far as "extra baggage" of a Control, isn't
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. :)

Well, to answer whether window messages are used for SynchronizationContext,
I can tell you (from Reflector), that WindowsFormsSynchronizationContext is
implemented on top of Control.Invoke and Control.BeginInvoke.

Also, Control.MarshaledInvoke (the underlying implementation of Invoke and
BeginInvoke) does use a windows message to notify the target thread. The
delegate itself is not passed using the message data (as far as I know you
can't safely pass addresses of garbage collectable objects asynchronously --
ok, yes you can with GCHandle). And Invoke-ing a delegate on your own
thread looks like it causes all invokes you've received from other threads
to be processed immediately instead of waiting for the message loop.
There's explicit locking everywhere and I'm glad I came up with my own
solution for my C++/CLI IO components (where all requests are actually
processed with APCs on a native thread running an alertable wait loop).
Maybe I need to think about using that lockless message queue to pass
delegates and make my own SynchronizationContext implementation -- but since
I don't control the message loop it wouldn't be nearly as elegant.
 

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

Similar Threads


Top