question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Simplify `Invoke` and `BeginInvoke` signature and accept `Action`

See original GitHub issue

Proposed API:

Add an overload that takes Action as a parameter for both Invoke(Action method) and BeginInvoke(Action method). I don’t think it is necessary to create overloads for methods that take params object[] args as it is possible to pass necessary parameters via a closure.

+        public IAsyncResult BeginInvoke(Action method)
         public IAsyncResult BeginInvoke(Delegate method)
         public IAsyncResult BeginInvoke(Delegate method, params object[] args)

+        public T Invoke<T>(Func<T> method)
+        public void Invoke(Action method)
         public object Invoke(Delegate method)
         public object Invoke(Delegate method, params object[] args)

Background

There are two golden rules for Windows Forms:

  1. Always interact with UI controls on the same thread as they are created. Interacting with controls from another thread requires InvokeRequired, Invoke, BeginInvoke, etc.
  2. Never execute a long-running piece of code in the UI thread.

Invoke and BeginInvoke take Delegate as their input parameters, which requires rather cumbersome boilerplate code, and frankly look dated by todays standard.

E.g.

private void UpdateUI() 
{
    if (myControl.InvokeRequired) 
    {
        myControl.Invoke(new MethodInvoker(() => { UpdateUI(); })); // <-- this is very cumbersome
    } 
    else
    {
        // we're on UI thread, update myControl
    }
}

⚠️ NB: The discussion of automatically switching to the UI thread is well outside the scope of this proposal. It will come separately.

The cumbersome bit is this:

myControl.Invoke(new MethodInvoker(() => UpdateUI()));
float area = 0;
listView1.Invoke(new Action(() => { area = CalculateArea(); }));

which can also be written as

myControl.Invoke((MethodInvoker)(() => { UpdateUI(); }));
myControl.Invoke((Action)(() => { UpdateUI(); }));

However it can not be written as this:

myControl.Invoke(() => UpdateUI());
// or 
myControl.Invoke(UpdateUI); // slightly inefficient - the delegate is not cached, https://github.com/dotnet/roslyn/issues/5835

~despite the fact Action is of a delegate type, just like MethodInvoker~ Thank you @weltkante for pointing it out.

float area = (float)listView1.Invoke(new Func<float>(() => CalculateArea()));

But now it will be possible to write something like this:

float area1 = listView1.Invoke(() => CalculateArea(width, length));
//
float area2 = listView1.Invoke(CalculateArea);

Perf considerations

The added benefit of the new API is that we will be creating smaller code footprint:

        private void InvokeDelegateFunc(object sender, EventArgs e)
        {
            // current
            float area = (float)listView1.Invoke(new Func<float>(() => CalculateArea()));
        }

        private void InvokeFunc(object sender, EventArgs e)
        {
            // proposed
            float area2 = listView1.Invoke(CalculateArea);
        }
InvokeDelegateFunc
.method private hidebysig 
	instance void InvokeDelegateFunc (
		object sender,
		class [System.Runtime]System.EventArgs e
	) cil managed 
{
	// Method begins at RVA 0x7104
	// Code size 31 (0x1f)
	.maxstack 3
	.locals init (
		[0] float32 area
	)

	// sequence point: (line 193, col 9) to (line 193, col 10) in C:\Development\winforms\src\System.Windows.Forms\tests\IntegrationTests\WinformsControlsTest\ListViewTest.cs
	IL_0000: nop
	// sequence point: (line 194, col 13) to (line 194, col 102) in C:\Development\winforms\src\System.Windows.Forms\tests\IntegrationTests\WinformsControlsTest\ListViewTest.cs
	IL_0001: ldarg.0
	IL_0002: ldfld class [System.Windows.Forms]System.Windows.Forms.ListView WinformsControlsTest.ListViewTest::listView1
	IL_0007: ldarg.0
	IL_0008: ldftn instance float32 WinformsControlsTest.ListViewTest::'<InvokeDelegateFunc>b__5_0'()
	IL_000e: newobj instance void class [System.Runtime]System.Func`1<float32>::.ctor(object, native int)
	IL_0013: callvirt instance object [System.Windows.Forms]System.Windows.Forms.Control::Invoke(class [System.Runtime]System.Delegate)
	IL_0018: unbox.any [System.Runtime]System.Single
	IL_001d: stloc.0
	// sequence point: (line 195, col 9) to (line 195, col 10) in C:\Development\winforms\src\System.Windows.Forms\tests\IntegrationTests\WinformsControlsTest\ListViewTest.cs
	IL_001e: ret
} // end of method ListViewTest::InvokeDelegateFunc

.method private hidebysig 
	instance float32 '<InvokeDelegateFunc>b__5_0' () cil managed 
{
	.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
		01 00 00 00
	)
	// Method begins at RVA 0x7a7c
	// Code size 7 (0x7)
	.maxstack 8

	// sequence point: (line 194, col 83) to (line 194, col 98) in C:\Development\winforms\src\System.Windows.Forms\tests\IntegrationTests\WinformsControlsTest\ListViewTest.cs
	IL_0000: ldarg.0
	IL_0001: call instance float32 WinformsControlsTest.ListViewTest::CalculateArea()
	IL_0006: ret
} // end of method ListViewTest::'<InvokeDelegateFunc>b__5_0'

vs

InvokeFunc
.method private hidebysig 
	instance void InvokeFunc (
		object sender,
		class [System.Runtime]System.EventArgs e
	) cil managed 
{
	// Method begins at RVA 0x7130
	// Code size 26 (0x1a)
	.maxstack 3
	.locals init (
		[0] float32 area2
	)

	// sequence point: (line 198, col 9) to (line 198, col 10) in C:\Development\winforms\src\System.Windows.Forms\tests\IntegrationTests\WinformsControlsTest\ListViewTest.cs
	IL_0000: nop
	// sequence point: (line 199, col 13) to (line 199, col 59) in C:\Development\winforms\src\System.Windows.Forms\tests\IntegrationTests\WinformsControlsTest\ListViewTest.cs
	IL_0001: ldarg.0
	IL_0002: ldfld class [System.Windows.Forms]System.Windows.Forms.ListView WinformsControlsTest.ListViewTest::listView1
	IL_0007: ldarg.0
	IL_0008: ldftn instance float32 WinformsControlsTest.ListViewTest::CalculateArea()
	IL_000e: newobj instance void class [System.Runtime]System.Func`1<float32>::.ctor(object, native int)
	IL_0013: callvirt instance !!0 [System.Windows.Forms]System.Windows.Forms.Control::Invoke<float32>(class [System.Runtime]System.Func`1<!!0>)
	IL_0018: stloc.0
	// sequence point: (line 200, col 9) to (line 200, col 10) in C:\Development\winforms\src\System.Windows.Forms\tests\IntegrationTests\WinformsControlsTest\ListViewTest.cs
	IL_0019: ret
} // end of method ListViewTest::InvokeFunc

Other considerations

I have checked emitted IL, and it appears to be of the same size, with the only difference use of Action instead of MethodInvoker.

@KlausLoeffelmann has highlighted to me that the issue appears to be C# specific, as VB understand the following right now:

        Friend Sub Test()
            Dim control As New Button()
            control.BeginInvoke(Sub()
                                    MessageBox.Show("Test!")
                                End Sub)
        End Sub

I have verified the proposal does not appear to have any negative impacts on VB.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:6
  • Comments:16 (15 by maintainers)

github_iconTop GitHub Comments

2reactions
KathleenDollardcommented, Apr 1, 2021

I think we should go forward with this. Sorry again for the delay.

1reaction
weltkantecommented, Feb 26, 2021

do you think if we introduced this, we would break a terribly awful lot of people with regards to what you wrote here?

Not really, no, people who wrote an extension method will compile against the new overload now (has higher priority than the extension method), but I really doubt someone would make an Invoke(Action) extension on Control and give it different behavior than Invoke(Delegate)

I expect existing extension methods are doing the same you are going to do in your overload so that should not be breaking.

Users who don’t recompile will still be calling their extension method regardless of what you introduce here, so at most its affecting source code, and you can be expected to update your code when you compile against a new framework version. (Though I don’t think its necessary in this case to update anything for most users.)

The comment in the other thread was just saying that having this method inbox is “nice to have” and people are already doing it manually via extension, but it wasn’t an argument against introducing it.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Dispatcher Invoke(...) vs BeginInvoke(...) confusion
When you use Dispatcher.BeginInvoke it means that it schedules the given action for execution in the UI thread at a later point in...
Read more >
Control.BeginInvoke Method (System.Windows.Forms)
BeginInvoke (Action)​​ Executes the specified delegate asynchronously on the thread that the control's underlying handle was created on.
Read more >
What's up with BeginInvoke?
BeginInvoke returns an IAsyncResult , just like the BeginInvoke method on any delegate. And you can use IAsyncResult to wait for the message...
Read more >
Update UI With WPF Dispatcher And TPL
BeginInvoke " schedules the given action for execution in the UI thread and then returns control to allow the current thread to continue ......
Read more >
Delegates and events
It specifies the signature of a method, and when you have a delegate instance, ... Delegates can also be run asynchronously if they...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found