2009-04-02 14 views
36

Sé que esta pregunta se ha hecho antes, pero estoy buscando una manera de:¿La mejor manera de invocar un código cruzado?

  1. agilizar la creación de código a través del roscado seguro.
  2. reutilice este código en cualquier situación (sin referencias a Windows Forms).

Esto es lo que tengo hasta ahora, pero quiero eliminar las referencias de Windows Forms. ¿Algunas 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(); 
    } 
} 

La clase anterior podría ser utilizado de esta manera:

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

¿Cómo puedo eliminar el System.Windows.Forms.Control en la clase SafeInvoke pero mantener la misma funcionalidad?

+0

Tenga en cuenta que los documentos que rodea Invoke () en Control son en realidad bastante sutiles.No creo que una clase genérica sea suficiente para Control debido a la interacción con IsHandleCreated y IsDisposed (a menos que siempre los verifique primero en su SafeInvokeDelegate). (http://stackoverflow.com/questions/714666/) –

+0

Gracias por compartir esta clase. Me ayudó a resolver mis problemas. –

Respuesta

86

También podría usar un método de extensión y lambdas para hacer su código mucho más limpio.

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

Así que ahora se puede utilizar en cualquier InvokeEx ISynchronizeInvoke y acceder a las propiedades y campos de aplicación de la clase.

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

Puede parecer obvio, pero también necesita agregar el espacio de nombres "Sistema" –

+0

Esta es una idea increíble. Lo hice en VB.net y también tengo 4 sobrecargas para Subrutinas/Funciones/WithParams/WithoutParams. VB.net puede inferir los tipos genéricos. – Eyal

+6

¿por qué no simplemente 'listView1.InvokeEx (lv => lv.Items.Clear());'? – jgauffin

10

Utilice ISynchronizeInvoke en lugar de Control. Esa es la interfaz que Control implementa con Invoke/BeginInvoke/EndInvoke/InvokeRequired.

Una alternativa es usar SynchronizationContext.Current - que es lo que usa BackgroundWorker, creo.

+0

¿Podría mostrar un ejemplo de código? :-) Implementar ISynchronizeInvoke requiere BeginInvoke, etc., y puede ser tedioso. – CLaRGe

+0

Parece que ISynchronizeInvoke solo está implementado por la clase Control. Esto no parece una forma de deshacerse de la dependencia de Windows Forms. – XOR

+0

No se requiere un ejemplo de código. Simplemente reemplace System.Windows.Forms.Control con System.ComponentModel.ISynchronizeInvoke. – Samuel

4

Aquí está en VB.net, muy similar a la respuesta de Samuel. Tengo cuatro sobrecargas dependiendo de si quiere una subrutina o función y si hay o no un parámetro. Sería fácil agregar más sobrecargas para más parámetros. VB.Net es capaz de inferir los tipos.

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 

Uso (funcionar esto en un 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 
+0

Hola. He estado usando su código anterior con gran éxito, pero probé mi programa en algunas computadoras. Ha funcionado correctamente en la mayoría de las computadoras Win XP en las que lo he probado, pero en una de ellas aparece el siguiente error: "Commom Language Runtime detectó un programa no válido". ¿Sabes cómo resolver esto? Estoy usando .NET framework V2. – Johan

+0

Intente actualizar el CLR? Creo que Sub lambda solo es compatible con .Net 4, antes solo había la función lambda. – Eyal

+0

Eso no se parece a la respuesta de Samuel para mí. Estás creando delegados. Él usa Lambdas en lugar de delegados. Publicaré el código VB que uso en mi propia respuesta. –

2

aquí está el código equivalente VB a la respuesta de Samuel que yo uso. note que en realidad tengo 2 funciones de extensiones, pero debo admitir que no sé por qué están ahí. Copié mi versión de C# hace años (tal vez de este sitio) y tenía ambas funciones de extensión, pero por qué razón, no entiendo completamente. Acabo de copiarlo y cómo usarlo, y medio entiendo todo lo que sucede bajo el capó con estas complicadas funciones.

#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 

Y la versión # C es:

#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 

de codificación feliz!

+1

Muy útil; especialmente la extensión que funciona con resultado de retorno. +1 – MiBol

+0

gracias, yo también. :PAG –

2

Hoy en día es fácil para invocar por ejemplo, decir que queremos invocar una etiqueta (lblVal) para obtener el valor de txtVal

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

tan fácil como eso: D

Cuestiones relacionadas