Melhor maneira de invocar qualquer código Cross-Threaded?
Pergunta
Eu sei que esta pergunta foi feita antes, mas eu estou procurando uma maneira de:
- agilizar a criação de código de cross-thread seguro.
- reutilizar este código em qualquer situação (sem Windows Forms referências).
Aqui está o que eu tenho até agora, mas eu quero remover as formas referências do Windows. Alguma idéia?
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();
}
}
A classe acima pode ser usado da seguinte maneira:
SafeInvoke _safeInvoker = new SafeInvoke();
void SafeClearItems()
{
_safeInvoker.Invoke(delegate
{
listView1.Items.Clear();
});
}
Como eu iria remover o System.Windows.Forms.Control na classe SafeInvoke mas manter a mesma funcionalidade?
Solução
Você também pode usar um método de extensão e lambdas para tornar seu código muito mais limpo.
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);
}
}
}
Então, agora você pode usar InvokeEx
em qualquer ISynchronizeInvoke e ser capaz de acessar as propriedades e áreas de implementação de classe.
this.InvokeEx(f => f.listView1.Items.Clear());
Outras dicas
Use ISynchronizeInvoke
vez de Control
. Essa é a interface que implementa Control
com Invoke/BeginInvoke/EndInvoke/InvokeRequired
.
Uma alternativa é usar SynchronizationContext.Current
-. que é o que os usos BackgroundWorker
, acredito
Hoje em dia é fácil de Invoke.
por exemplo. Dizer que queremos invocar uma etiqueta (lblVal) para obter o valor de txtVal
lblVal.Invoke((MethodInvoker)delegate{lblVal.Text = txtVal.Text;});
... é tão fácil como isso: D
Aqui está em VB.net, muito semelhante a resposta de Samuel. Tenho quatro sobrecargas, dependendo se você quer um sub-rotina ou função e se há ou não um parâmetro. Seria fácil para adicionar mais sobrecargas para mais parâmetros. VB.Net é capaz de inferir os 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 (executar este em um 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
aqui está o código equivalente VB para a resposta de Samuel que eu uso. Repare que eu realmente tem 2 extensões de funções, mas devo admitir que eu não sei por que eles estão lá. eu copiei o meu C # anos atrás versão (talvez a partir deste site) e tinha as duas funções de extensão, mas por que razão, eu não entendo totalmente. i apenas copiou-lo e como usá-lo, e eu metade entender tudo o que se passa 'sob a capa' com estas funções complicadas.
#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 a versão 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
Happy codificação!