调用任何交叉线程代码的最佳方法?

时间:2009-04-02 20:10:27

标签: c# .net invoke

我知道之前已经问过这个问题,但我正在寻找一种方法:

  1. 简化安全交叉线程代码的创建。
  2. 在任何情况下重复使用此代码(无Windows窗体引用)。
  3. 这是我到目前为止所做的,但我想删除Windows窗体引用。有什么想法吗?

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

    上述类可能会以这种方式使用:

    SafeInvoke _safeInvoker = new SafeInvoke();
    void SafeClearItems()
    {
        _safeInvoker.Invoke(delegate
            {
                listView1.Items.Clear();
            });
    }
    

    如何删除SafeInvoke类中的System.Windows.Forms.Control但保留相同的功能?

5 个答案:

答案 0 :(得分:90)

您还可以使用扩展方法和lambdas来使代码更清晰。

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

所以现在你可以在任何ISynchronizeInvoke上使用InvokeEx,并且能够访问实现类的属性和字段。

this.InvokeEx(f => f.listView1.Items.Clear());

答案 1 :(得分:10)

使用ISynchronizeInvoke代替Control。这是ControlInvoke/BeginInvoke/EndInvoke/InvokeRequired实现的接口。

另一种方法是使用SynchronizationContext.Current - 这是BackgroundWorker使用的内容,我相信。

答案 2 :(得分:7)

现在很容易调用 例如假设我们想要调用Label(lblVal)来获取txtVal的值

lblVal.Invoke((MethodInvoker)delegate{lblVal.Text = txtVal.Text;});

......它很简单:D

答案 3 :(得分:4)

这是在VB.net中,与Samuel的答案非常相似。根据您是否需要子程序或函数以及是否有参数,我有四个重载。为更多参数添加更多重载会很容易。 VB.Net能够推断出类型。

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

用法(在后台工作中运行):

    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

答案 4 :(得分:3)

这是我使用的Samuel的答案的VB等效代码。注意我实际上有2个扩展功能,但我必须承认我不知道他们为什么在那里。我多年前复制了我的C#版本(也许来自这个网站)并且它有两个扩展功能,但由于什么原因,我不完全理解。我只是复制了它以及如何使用它,我一半了解所有那些带有这些复杂功能的“引擎盖”。

#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

C#版本是:

#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

快乐的编码!