Best Way to Invoke Any Cross-Threaded Code?

Deadly 提交于 2019-11-26 15:43:17

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());

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.

Tj Laubscher

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

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

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!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!