Best Way to Invoke Any Cross-Threaded Code?
Solution 1
You also could use an extension method and lambdas to make your code much cleaner.
using System.ComponentModel;
public static class ISynchronizeInvokeExtensions
{
public static void InvokeEx<T>(this T @this, Action<T> action) where T : ISynchronizeInvoke
{
if (@this.InvokeRequired)
{
@this.Invoke(action, new object[] { @this });
}
else
{
action(@this);
}
}
}
So now you can use InvokeEx
on any ISynchronizeInvoke and be able to access the properties and fields of implementing class.
this.InvokeEx(f => f.listView1.Items.Clear());
Solution 2
Use ISynchronizeInvoke
instead of Control
. That's the interface that Control
implements with Invoke/BeginInvoke/EndInvoke/InvokeRequired
.
An alternative is to use SynchronizationContext.Current
- which is what BackgroundWorker
uses, I believe.
Solution 3
Nowadays it's easy to Invoke.
e.g. Say we want to invoke a Label(lblVal) to get value of txtVal
lblVal.Invoke((MethodInvoker)delegate{lblVal.Text = txtVal.Text;});
...it's as easy as that :D
Solution 4
Here it is in VB.net, very similar to Samuel's answer. I have four overloads depending on whether you want a subroutine or function and whether or not there's a parameter. It would be easy to add more overloads for more parameters. VB.Net is able to infer the types.
Module ISynchronizeInvokeExtensions
Public Delegate Function GenericLambdaFunctionWithParam(Of InputType, OutputType)(ByVal input As InputType) As OutputType
Private Delegate Function InvokeLambdaFunctionCallback(Of InputType, OutputType)(ByVal f As GenericLambdaFunctionWithParam(Of InputType, OutputType), ByVal input As InputType, ByVal c As System.ComponentModel.ISynchronizeInvoke) As OutputType
Public Function InvokeEx(Of InputType, OutputType)(ByVal f As GenericLambdaFunctionWithParam(Of InputType, OutputType), ByVal input As InputType, ByVal c As System.ComponentModel.ISynchronizeInvoke) As OutputType
If c.InvokeRequired Then
Dim d As New InvokeLambdaFunctionCallback(Of InputType, OutputType)(AddressOf InvokeEx)
Return DirectCast(c.Invoke(d, New Object() {f, input, c}), OutputType)
Else
Return f(input)
End If
End Function
Public Delegate Sub GenericLambdaSubWithParam(Of InputType)(ByVal input As InputType)
Public Sub InvokeEx(Of InputType)(ByVal s As GenericLambdaSubWithParam(Of InputType), ByVal input As InputType, ByVal c As System.ComponentModel.ISynchronizeInvoke)
InvokeEx(Of InputType, Object)(Function(i As InputType) As Object
s(i)
Return Nothing
End Function, input, c)
End Sub
Public Delegate Sub GenericLambdaSub()
Public Sub InvokeEx(ByVal s As GenericLambdaSub, ByVal c As System.ComponentModel.ISynchronizeInvoke)
InvokeEx(Of Object, Object)(Function(i As Object) As Object
s()
Return Nothing
End Function, Nothing, c)
End Sub
Public Delegate Function GenericLambdaFunction(Of OutputType)() As OutputType
Public Function InvokeEx(Of OutputType)(ByVal f As GenericLambdaFunction(Of OutputType), ByVal c As System.ComponentModel.ISynchronizeInvoke) As OutputType
Return InvokeEx(Of Object, OutputType)(Function(i As Object) f(), Nothing, c)
End Function
End Module
Usage (run this in a backgroundworker):
InvokeEx(Sub(x As String) Me.Text = x, "foo", Me) 'set form title to foo
InvokeEx(AddressOf MsgBox, Me.Text, Me)
InvokeEx(Sub() Me.Text &= "!", "foo", Me) 'append "!" to form title
InvokeEx(AddressOf MsgBox, Me.Text, Me)
Dim s As String = InvokeEx(Function() Me.Text, Me) & "bar" 'get form title to backgorundworker thread
InvokeEx(AddressOf MsgBox, s, Me) 'display the string from backgroundworker thread
Solution 5
here's the VB equivalent code to Samuel's answer that i use. notice i actually have 2 extensions functions, but i must admit i don't know why they are there. i copied my C# version years ago (maybe from this site) and it had both extension functions, but for what reason, i don't fully understand. i just copied it and how to use it, and i half understand all that goes on 'under the hood' with these complicated functions.
#Const System_ComponentModel = True
#Const System_Drawing = False
Option Compare Binary
Option Explicit On
Option Strict On
Imports System.Collections
Imports System.Runtime.CompilerServices ' for Extension() attribute
Imports System.Text
#If System_ComponentModel Then
Imports System.ComponentModel
#End If
#If System_Drawing Then
Imports System.Drawing
#End If
Public Module MyExtensions
' other #Region blocks are removed. i use many in my Extensions
' module/class. the below code is only the 2 relevant extension
' for this 'SafeInvoke' functionality. but i use other regions
' such as "String extensions" and "Numeric extensions". i use
' the above System_ComponentModel and System_Drawing compiler
' directives to include or exclude blocks of code that i want
' to either include or exclude in a project, which allows me to
' easily compare my code in one project with the same file in
' other projects to syncronise new changes across projects.
' you can scrap pretty much all the code above,
' but i'm giving it here so you see it in the full context.
#Region "ISynchronizeInvoke extensions"
#If System_ComponentModel Then
<Extension()>
Public Function SafeInvoke(Of T As ISynchronizeInvoke, TResult)(isi As T, callFunction As Func(Of T, TResult)) As TResult
If (isi.InvokeRequired) Then
Dim result As IAsyncResult = isi.BeginInvoke(callFunction, New Object() {isi})
Dim endresult As Object = isi.EndInvoke(result)
Return DirectCast(endresult, TResult)
Else
Return callFunction(isi)
End If
End Function
''' <summary>
''' This can be used in VB with:
''' txtMyTextBox.SafeInvoke(Sub(d) d.Text = "This is my new Text value.")
''' or:
''' txtMyTextBox.SafeInvoke(Sub(d) d.Text = myTextStringVariable)
''' </summary>
''' <typeparam name="T"></typeparam>
''' <param name="isi"></param>
''' <param name="callFunction"></param>
''' <remarks></remarks>
<Extension()>
Public Sub SafeInvoke(Of T As ISynchronizeInvoke)(isi As T, callFunction As Action(Of T))
If isi.InvokeRequired Then
isi.BeginInvoke(callFunction, New Object() {isi})
Else
callFunction(isi)
End If
End Sub
#End If
#End Region
' other #Region blocks are removed from here too.
End Module
And the C# version is:
#define System_ComponentModel
#undef System_Drawing
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#if System_ComponentModel
using System.ComponentModel;
#endif
#if System_Drawing
using System.Drawing;
#endif
namespace MyCompany.Extensions
{
static partial class MyExtensions
{
// other #Region blocks are removed. i use many in my Extensions
// module/class. the below code is only the 2 relevant extension
// for this 'SafeInvoke' functionality. but i use other regions
// such as "String extensions" and "Numeric extensions". i use
// the above System_ComponentModel and System_Drawing compiler
// directives to include or exclude blocks of code that i want
// to either include or exclude in a project, which allows me to
// easily compare my code in one project with the same file in
// other projects to syncronise new changes across projects.
// you can scrap pretty much all the code above,
// but i'm giving it here so you see it in the full context.
#region ISynchronizeInvoke extensions
#if System_ComponentModel
public static TResult SafeInvoke<T, TResult>(this T isi, Func<T, TResult> callFunction) where T : ISynchronizeInvoke
{
if (isi.InvokeRequired)
{
IAsyncResult result = isi.BeginInvoke(callFunction, new object[] { isi });
object endResult = isi.EndInvoke(result); return (TResult)endResult;
}
else
return callFunction(isi);
}
/// <summary>
/// This can be used in C# with:
/// txtMyTextBox.SafeInvoke(d => d.Text = "This is my new Text value.");
/// or:
/// txtMyTextBox.SafeInvoke(d => d.Text = myTextStringVariable);
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="isi"></param>
/// <param name="callFunction"></param>
public static void SafeInvoke<T>(this T isi, Action<T> callFunction) where T : ISynchronizeInvoke
{
if (isi.InvokeRequired) isi.BeginInvoke(callFunction, new object[] { isi });
else
callFunction(isi);
}
#endif
#endregion
// other #Region blocks are removed from here too.
} // static class MyExtensions
} // namespace
Happy coding!
CLaRGe
I used to be a programmer that became a help desk guy, but I hated that job more than death. So I quit and became an entrepreneur and liked it alot, but went broke. So now I'm an programmer with a business and I love it.
Updated on December 18, 2020Comments
-
CLaRGe over 3 years
I know that this question has been asked before, but I'm looking for a way to:
- streamline the creation of safe cross-threaded code.
- reuse this code in any situation (no Windows Forms references).
Here's what I have so far, but I want to remove the Windows Forms references. Any ideas?
public delegate void SafeInvokeDelegate(System.Action action); public class SafeInvoke { private readonly System.Windows.Forms.Control _threadControl; public SafeInvoke() { _threadControl = new System.Windows.Forms.Control(); } public void Invoke(System.Action action) { if (_threadControl.InvokeRequired) _threadControl.Invoke(new SafeInvokeDelegate(Invoke), new object[] {action}); else if (action != null) action(); } }
The above class might be used this way:
SafeInvoke _safeInvoker = new SafeInvoke(); void SafeClearItems() { _safeInvoker.Invoke(delegate { listView1.Items.Clear(); }); }
How would I remove the System.Windows.Forms.Control in the SafeInvoke class but keep the same functionality?
-
CLaRGe about 15 yearsCould you show a code example? :-) Implementing ISynchronizeInvoke requires BeginInvoke, etc, and could get tedious.
-
Raymond about 15 yearsLooks like ISynchronizeInvoke is implemented only by Control class. This doesn't looke like a way to get rid of Windows Forms dependency.
-
Samuel about 15 years@XOR: ISynchronizeInvoke is in the System assembly, I don't think you will need to reference the Forms assembly.
-
Samuel about 15 years@XOR: Confirmed, ISynchronizeInvoke only requires a reference to the System library.
-
btype over 14 yearsIt may seem obvious, but you also need to add the "System" namespace
-
Eyal over 13 yearsThis is an awesome idea. I did it in VB.net and also have 4 overloads for Subroutines/Functions/WithParams/WithoutParams. VB.net is able to infer the generic types.
-
Kritz over 12 yearsHi. I've have been using your code above with great success, but I tested my program on a few computers. It has worked correctly on most of the Win XP computers that I've tested it on, but on one of them I get the following error: "Commom Language Runtime detected an invalid program". Do you know how to solve this? I am using .NET framework V2.
-
Eyal over 12 yearsTry upgrading the CLR? I think that Sub lambda is supported only by .Net 4, before there there was only Function lambda.
-
jgauffin over 12 yearswhy not simply
listView1.InvokeEx(lv => lv.Items.Clear());
? -
Mark Kram over 11 yearsYou sir are a genius, this solution solved my problem, Thank You!
-
Shawn Kovac over 10 yearsThat doesn't look similar to Samuel's answer to me. You are creating delegates. He uses Lambdas instead of delegates. I will post the VB code that i use in my own answer.
-
Eyal over 10 yearsThe code that I wrote is quite old. At the time, I think that VB.Net didn't have full lambda support.
-
MiBol about 9 yearsVery useful; specially the extension that works with return result. +1
-
Stix over 8 yearsThis piece of code is fabulistastic. Makes my code nice and clean and easy! As Mark who reminded me of Gump said - Samuel you are a Genius! I like it especially because it works!
-
rvpals almost 8 yearsCan someone please show me what I need to do to incorporate this code in my own form? I have a FormMain (My mainform), I put the block of code in my FormMain.cs class (outside of class FormMain). I got a weird error on the FormMain.designer.cs about not finding the "$this.icon".
-
Arman almost 6 years@jgauffin Some controls may not implement ISynchronizeInvoke so you cannot use this extension method on them directly. Ex. ToolStripStatusLabel does not implement ISynchronizeInvoke.
-
jgauffin almost 6 years@arman: you can change the
T
restriction toControl
or similar if that fits your use case better. -
ShrapNull over 5 yearsWhat a great, straightforward solution. Thank you
-
Ctrl S over 5 yearsThis works perfectly. Just needed to capitalize
invoke
-->Invoke
.