2009-04-02 5 views
36

Je sais que cette question a été posée, mais je suis à la recherche d'un moyen de:Meilleure façon d'invoquer un code croisé?

  1. rationalisent la création de code-thread croix en toute sécurité.
  2. réutiliser ce code dans n'importe quelle situation (pas de références Windows Forms).

Voici ce que j'ai jusqu'à présent, mais je veux supprimer les références Windows Forms. Des idées?

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 classe ci-dessus peut être utilisé de cette façon:

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

Comment pourrais-je supprimer le System.Windows.Forms.Control dans la classe SafeInvoke mais garder les mêmes fonctionnalités?

+0

Notez que la documentation entourant Invoke () sur le contrôle sont en réalité assez subtiles.Je ne crois pas qu'un cours générique soit suffisant pour Control à cause de l'interaction avec IsHandleCreated et IsDisposed (à moins que vous ne vérifiiez toujours ceux-ci en premier dans votre SafeInvokeDelegate). (http://stackoverflow.com/questions/714666/) –

+0

Merci d'avoir partagé cette classe. A m'a aidé à résoudre mes problèmes .. –

Répondre

86

Vous pouvez également utiliser une méthode d'extension et lambdas pour rendre votre code beaucoup plus propre.

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

Alors maintenant, vous pouvez utiliser InvokeEx sur tout ISynchronizeInvoke et être en mesure d'accéder aux propriétés et domaines de la mise en œuvre de la classe.

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

Cela peut sembler évident, mais vous devez également ajouter l'espace de noms "System" –

+0

Ceci est une idée géniale. Je l'ai fait dans VB.net et j'ai aussi 4 surcharges pour Subroutines/Functions/WithParams/WithoutParams. VB.net est capable de déduire les types génériques. – Eyal

+6

pourquoi pas simplement 'listView1.InvokeEx (lv => lv.Items.Clear());'? – jgauffin

10

Utilisez ISynchronizeInvoke au lieu de Control. C'est l'interface que Control implémente avec Invoke/BeginInvoke/EndInvoke/InvokeRequired.

Une alternative est d'utiliser SynchronizationContext.Current - qui est ce que BackgroundWorker utilise, je crois.

+0

Pourriez-vous montrer un exemple de code? :-) Implémenter ISynchronizeInvoke nécessite BeginInvoke, etc, et pourrait devenir fastidieux. – CLaRGe

+0

On dirait que ISynchronizeInvoke est implémenté uniquement par la classe Control. Cela ne ressemble pas à un moyen de se débarrasser de la dépendance aux Windows Forms. – XOR

+0

Aucun exemple de code requis. Vous remplacez simplement System.Windows.Forms.Control par System.ComponentModel.ISynchronizeInvoke. – Samuel

4

Ici, c'est dans VB.net, très similaire à la réponse de Samuel. J'ai quatre surcharges selon que vous voulez un sous-programme ou une fonction et si oui ou non il y a un paramètre. Il serait facile d'ajouter plus de surcharges pour plus de paramètres. VB.Net est capable de déduire les 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 

Utilisation (exécuter ce dans 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

Salut. J'ai utilisé votre code ci-dessus avec beaucoup de succès, mais j'ai testé mon programme sur quelques ordinateurs. Il a fonctionné correctement sur la plupart des ordinateurs Win XP sur lesquels je l'ai testé, mais sur l'un d'eux, j'ai l'erreur suivante: "Commom Language Runtime a détecté un programme invalide". Savez-vous comment résoudre ce problème? J'utilise .NET framework V2. – Johan

+0

Essayez de mettre à jour le CLR? Je pense que Sub lambda n'est supporté que par .Net 4, avant il n'y avait que la fonction lambda. – Eyal

+0

Cela ne ressemble pas à la réponse de Samuel à moi. Vous créez des délégués. Il utilise Lambdas au lieu de délégués. Je vais poster le code VB que j'utilise dans ma propre réponse. –

2

est ici le code équivalent VB à la réponse de Samuel que je l'utilise. remarquez que j'ai réellement 2 fonctions d'extensions, mais je dois admettre que je ne sais pas pourquoi ils sont là. J'ai copié ma version C# il y a des années (peut-être à partir de ce site) et il avait les deux fonctions d'extension, mais pour quelle raison, je ne comprends pas complètement. Je l'ai juste copié et comment l'utiliser, et je comprends à moitié tout ce qui se passe 'sous le capot' avec ces fonctions compliquées.

#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 

Et la version C# est:

#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 

Bonne programmation!

+1

Très utile; spécialement l'extension qui fonctionne avec le résultat de retour. +1 – MiBol

+0

merci, je le fais aussi. : P –

2

Maintenant, un jour il est facile de Invoke par exemple que nous voulons invoquer une étiquette (lblVal) pour obtenir la valeur de txtVal

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

aussi simple que cela: D

Questions connexes