可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I know that this question has been asked before, but I'm looking for a way to:
- streamline the creation of safe cross-threaded code.
- reuse this code in any situation (no Windows Forms references).
Here's what I have so far, but I want to remove the Windows Forms references. Any 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(); } }
The above class might be used this way:
SafeInvoke _safeInvoker = new SafeInvoke(); void SafeClearItems() { _safeInvoker.Invoke(delegate { listView1.Items.Clear(); }); }
How would I remove the System.Windows.Forms.Control in the SafeInvoke class but keep the same functionality?
回答1:
You also could use an extension method and lambdas to make your code much cleaner.
using System.ComponentModel; public static class ISynchronizeInvokeExtensions { public static void InvokeEx(this T @this, Action action) where T : ISynchronizeInvoke { if (@this.InvokeRequired) { @this.Invoke(action, new object[] { @this }); } else { action(@this); } } }
So now you can use InvokeEx
on any ISynchronizeInvoke and be able to access the properties and fields of implementing class.
this.InvokeEx(f => f.listView1.Items.Clear());
回答2:
Use ISynchronizeInvoke
instead of Control
. That's the interface that Control
implements with Invoke/BeginInvoke/EndInvoke/InvokeRequired
.
An alternative is to use SynchronizationContext.Current
- which is what BackgroundWorker
uses, I believe.
回答3:
Here it is in VB.net, very similar to Samuel's answer. I have four overloads depending on whether you want a subroutine or function and whether or not there's a parameter. It would be easy to add more overloads for more parameters. VB.Net is able to infer the 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
Usage (run this in a 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
回答4:
here's the VB equivalent code to Samuel's answer that i use. notice i actually have 2 extensions functions, but i must admit i don't know why they are there. i copied my C# version years ago (maybe from this site) and it had both extension functions, but for what reason, i don't fully understand. i just copied it and how to use it, and i half understand all that goes on 'under the hood' with these complicated functions.
#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 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 ''' ''' 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) ''' ''' ''' ''' ''' 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
And the C# version is:
#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(this T isi, Func 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); } /// /// 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); /// /// /// /// public static void SafeInvoke(this T isi, Action 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 coding!
回答5:
Now a days it's Easy to Invoke e.g say we want to invoke a Label(lblVal) to get value of txtVal
lblVal.invoke((MethodInvoker)delegate{txtVal.Text = lblVal.Text;});
as easy as that :D