why there is no EndInvoke in Cross thread UI component call ?
Asked Answered
R

2

7

I have been trying to add some string to some ListBox that i have on my application ( simple winform ) - and i did it in using BeginInboke

 myListBox.BeginInvoke(new Action(delegate()
 {
       myListBox.Items.Add( "some string" )); 
 }));

After i reading those 3 lines again - and i don't understand why in any example of Cross thread UI that i look on google and on MSDN i don't see any call of EndInvoke ? Is there some reason to not call the EndInvoke on this case ?

Resuscitator answered 2/8, 2013 at 12:2 Comment(0)
F
20

This was an unfortunate naming choice in .NET. The Control.BeginInvoke and Dispatcher.BeginInvoke methods have the same name as a delegate's methods but operate completely different. The chief differences:

  • A delegate's BeginInvoke() method is always type-safe, it has the exact same arguments as the delegate declaration. This is entirely missing from the Control/Dispatcher versions, arguments are passed through a params array of type object[]. The compiler will not tell you when you get an argument wrong, it bombs at runtime

  • A delegate's Invoke() method runs the delegate target on the same thread. Not the case for Control/Dispatcher.Invoke(), they marshal the call to the UI thread

  • An exception that's thrown in a delegate's BeginInvoke() target is captured and does not cause the program to fail. To be re-thrown when you call EndInvoke(). This is not the case at all for Control/Dispatcher.BeginInvoke(), they raise the exception on the UI thread. With no decent way to catch the exception, one of the bigger reasons that Application.UnhandledException exists.

  • Calling a delegate's EndInvoke() method is required, it causes a 10 minutes resource leak if you don't. It is not required for the Control/Dispatcher.BeginInvoke() methods and you never do so in practice.

  • A delegate's BeginInvoke() method relies on the CLR's remoting infrastructure to serialize the method arguments and method result to/from the worker thread. This plumbing was removed in .NET Core (aka .net5+), BeginInvoke now throws an exception. No such problem with Control/Dispatcher.BeginInvoke().

  • Using Control/Dispatcher.Invoke() is risky, it is quite liable to cause deadlock. Triggered when the UI thread isn't ready to invoke the target and does something unwise like waiting for a thread to complete. Not a problem for a delegate, not in the least because its Invoke() method doesn't use a thread.

  • Calling Control/Dispatcher.BeginInvoke() on the UI thread is a supported scenario. The target still runs on the UI thread, as expected. But later, after the UI thread goes idle again and re-enters the dispatcher loop. This is actually a very useful feature, it helps solve tricky re-entrancy problems. Particularly in event handlers for UI controls that will misbehave when you run code with too many side-effects.

A big list with heavy implementation details. The TLDR version is certainly: "They have nothing in common, not calling EndInvoke is fine and entirely normal".

Feudalize answered 2/8, 2013 at 14:31 Comment(2)
Great answer! That comparison list between Control.…Invoke and TDelegate.…Invoke ought to be on MSDN. -- I'd only suggest that you explain about WPF/Silverlight's Dispatcher in a side-note, since it doesn't exist in Windows Forms (which is the context of this question).Stercoraceous
Thanks for this - I've been stepping through Control.cs wondering how to fix a null Queue: threadCallBackList. Now I can rest easy and remove the EndInvoke causing the issue! :)Outrun
S
6

Control.BeginInvoke does not appear to fully follow the usual BeginX/EndX pattern a.k.a. the Asynchronous Programming Model (APM). Usually, you must call EndX for each BeginX, but in the case of Control.BeginInvoke, this is not strictly required:

"You can call EndInvoke to retrieve the return value from the delegate, if neccesary, but this is not required. EndInvoke will block until the return value can be retrieved."

— from the Remarks section on the MSDN reference page for Control.BeginInvoke (emphasis by me)

And in practice, it is hardly ever necessary. This is because the method is usually called to let some code execute on the UI thread that updates the UI. Updating the UI won't normally produce any return value, therefore you wouldn't want to call EndInvoke.

Stercoraceous answered 2/8, 2013 at 12:7 Comment(4)
Deleted my post as by the time I'd edited it a few times, it ended up looking a lot like yours :)Keheley
From what i know - if you don't call the EndInvoke to close the IAsyncResult that was return from the BeginInvoke - i will get memory leak - please correct me if i wrongResuscitator
@Yanshof: In this specific case (Windows Forms' Control.BeginInvoke), there is no indication of a memory leak when not calling Control.EndInvoke. After all, this is a widespread practice, and a officially documented pattern. If it caused memory leaks, someone would have been bound to notice sometime during the many years of Windows Forms' existence.Stercoraceous
If you take a look at the source code for the Control class (github/dotnet/winforms) and specifically the use of the ThreadMethodEntry class (which is the IAsyncResult object used for the Windows Forms Begin/End pattern) in the MarshaledInvoke and InvokeMarshalledCallbacks you'd realize that IAsyncResult objects are processed even without EndInvoke, so that they will be GCed.Pokpoke

© 2022 - 2024 — McMap. All rights reserved.