2009-04-02 6 views
36

So che questo problema è stato fatto prima, ma sto cercando un modo per:Il modo migliore per richiamare qualsiasi codice cross-threaded?

  1. semplificare la creazione di codice cross-threaded di sicurezza.
  2. riutilizzare questo codice in qualsiasi situazione (senza riferimenti a Windows Form).

Ecco quello che ho finora, ma voglio rimuovere i riferimenti di Windows Form. Qualche idea?

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 di cui sopra potrebbe essere usata in questo modo:

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

Come dovrei togliere lo System.Windows.Forms.Control nella classe SafeInvoke ma mantenere la stessa funzionalità?

+0

Si noti che la documentazione che circonda Invoke () su Control sono in realtà piuttosto sottili.Non credo che una classe generica sia sufficiente per il controllo a causa dell'interazione con IsHandleCreated e IsDisposed (a meno che non si controllino sempre i primi in SafeInvokeDelegate). (http://stackoverflow.com/questions/714666/) –

+0

Grazie per aver condiviso questo corso. Mi ha aiutato a risolvere i miei problemi .. –

risposta

86

È anche possibile utilizzare un metodo di estensione e lambda per rendere il codice molto più pulito.

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

Così ora si può utilizzare su qualsiasi InvokeEx ISynchronizeInvoke ed essere in grado di accedere alle proprietà e campi di attuazione di classe.

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

Può sembrare ovvio, ma è anche necessario aggiungere lo spazio dei nomi "Sistema" –

+0

Questa è un'idea fantastica. L'ho fatto in VB.net e ho anche 4 overload per Subroutine/Funzioni/WithParams/WithoutParams. VB.net è in grado di inferire i tipi generici. – Eyal

+6

perché non semplicemente 'listView1.InvokeEx (lv => lv.Items.Clear());'? – jgauffin

10

Utilizzare ISynchronizeInvoke anziché Control. Questa è l'interfaccia con gli strumenti Control con Invoke/BeginInvoke/EndInvoke/InvokeRequired.

Un'alternativa è quella di utilizzare SynchronizationContext.Current - che è quello che utilizza gli BackgroundWorker, credo.

+0

Puoi mostrare un esempio di codice? :-) L'implementazione di ISynchronizeInvoke richiede BeginInvoke, ecc. E potrebbe diventare noioso. – CLaRGe

+0

Sembra che ISynchronizeInvoke sia implementato solo dalla classe Control. Questo non si aggira come un modo per sbarazzarsi della dipendenza di Windows Form. – XOR

+0

Non è richiesto alcun esempio di codice. Basta sostituire System.Windows.Forms.Control con System.ComponentModel.ISynchronizeInvoke. – Samuel

4

Eccolo in VB.net, molto simile alla risposta di Samuele. Ho quattro sovraccarichi a seconda che si voglia una subroutine o una funzione e se ci sia o meno un parametro. Sarebbe facile aggiungere più sovraccarichi per più parametri. VB.Net è in grado di dedurre i tipi.

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 (eseguire questo in 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

Ciao. Ho usato il tuo codice in alto con grande successo, ma ho testato il mio programma su alcuni computer. Ha funzionato correttamente sulla maggior parte dei computer Win XP che ho provato, ma su uno di essi viene visualizzato il seguente errore: "Commom Language Runtime ha rilevato un programma non valido". Sai come risolvere questo? Sto usando .NET Framework V2. – Johan

+0

Prova ad aggiornare il CLR? Penso che Sub lambda sia supportato solo da .Net 4, prima c'era solo Function lambda. – Eyal

+0

Non sembra simile alla risposta di Samuele a me. Stai creando delegati. Usa Lambdas invece dei delegati. Pubblicherò il codice VB che uso nella mia risposta. –

2

ecco il codice equivalente VB per la risposta di Samuel che uso. avviso ho effettivamente 2 funzioni di estensione, ma devo ammettere che non so perché sono lì. Ho copiato la mia versione C# anni fa (forse da questo sito) e aveva entrambe le funzioni di estensione, ma per quale motivo, non capisco completamente. l'ho appena copiato e come usarlo, e a metà capisco tutto ciò che accade sotto le casse con queste complicate funzioni.

#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 

E il C# versione è:

#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 

Felice codifica!

+1

Molto utile; specialmente l'estensione che funziona con il risultato di ritorno. +1 – MiBol

+0

grazie, lo sono anche io. : P –

2

Ora un giorno è facile Invoke per esempio dire che vogliamo invocare un Label (lblVal) per ottenere il valore di txtVal

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

facile come questo: D