类型安全的 Control.Invoke C#
Posted
技术标签:
【中文标题】类型安全的 Control.Invoke C#【英文标题】:type-safe Control.Invoke C# 【发布时间】:2020-04-29 18:43:00 【问题描述】:我正在使用 C# 编写一个包含 2 个线程的软件
-
控制窗体(Windows 窗体)并与用户交互的线程。
在后台检查在线数据的线程。
当在线数据不规则时,我需要第二个线程在表单上打印消息。
因为只有创建控件的线程才能更改它,所以我使用委托。 第二个线程通过 Control.Invoke 方法调用第一个线程执行委托。
例子:
public partial class Form1 : Form
public delegate void SafeCallDelegate(string text);
public static SafeCallDelegate d;
public Form1()
InitializeComponent();
d = new SafeCallDelegate(addText);
private static void addText(string text)
richTextBox1.Text += text + "\n";
static class Program
static Thread secondThread;
[STAThread]
static void Main()
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
secondThread = new Thread(SecondThreadFunc);
secondThread.Start();
Application.Run(new Form1());
static void SecondThreadFunc()
int counter = 0;
do
if (Form.ActiveForm == null) continue;
Form.ActiveForm.Invoke(Form1.d, new object[] "SecondThreadFunc counter: " + counter.ToString() );
counter++;
Thread.Sleep(1000);
while (true);
我可以接受这个不太干净的解决方案,但我的问题是这根本不是类型安全的。
Control.Invoke 函数接受一个对象数组,而不管委托需要什么,这可能导致运行时异常。
有没有一种更安全的方法可以解决我的问题?
谢谢。
【问题讨论】:
不是解决方案,但我通常会这样做。创建一个可以从任何线程调用的函数,但能够执行特定于 UI 线程的工作。使用Control.InvokeRequired
测试调用者是否不在 UI 线程上。如果是这样,调用相同的函数(有效地递归)。这样,非类型安全代码就在一个地方,就在调用要到达的地方。抱歉,我正在打电话,但这是一种很常见的模式,您应该可以找到示例
是的,我知道这个解决方案,这是我试图避免的解决方案。我只是发现 if(Control.InvokeRequired) 解决方案一点也不优雅。我觉得这么简单很奇怪(我认为很常见的情况)没有任何优雅的解决方案。
编写正确的异步代码并简单地从主线程await
...
您可能会构建一个 lambda,在运行时使用表达式树调用您的委托?
【参考方案1】:
不要将参数传递给Invoke
,而是将它们作为closured variable 在委托中传递。
所以,不要这样:
Form.ActiveForm.Invoke(Form1.d, new object[] "SecondThreadFunc counter: " + counter.ToString() );
这样写:
Form.ActiveForm.Invoke
(
new Action
(
() => Form1.d("SecondThreadFunc counter: " + counter.ToString())
)
);
这避免了将参数传递给Invoke
的问题并消除了类型安全问题。
如果你觉得这有点罗嗦,你也可以定义一个简单的辅助扩展方法:
static class Helpers
static public void MyInvoke(this Control control, Action action)
if (control.InvokeRequired)
control.Invoke(action);
else
action();
现在你只需要在你的主表单中写下这样的内容:
this.MyInvoke( () => addText("SecondThreadFunc counter: " + counter.ToString()));
您现在可以摆脱所有 SafeCallDelegate 的东西,只需在需要时传递您想要的任何 lambda。
【讨论】:
【参考方案2】:据我了解,您希望构建 Action<...> 或 Func<...> 代表尊重 Control Invoke 要求。如果是这样,您可以像这样装饰现有的通用委托:
public static class InvokeFunc
private class InvokeFuncImpl<TRusult>
public Func<TRusult> Func get;
public InvokeFuncImpl(Func<TRusult> f)
Func = f;
public static implicit operator Func<TRusult>(InvokeFuncImpl<TRusult> value)
return () =>
if(Form.ActiveForm == null)
return value.Func();
if(!Form.ActiveForm.InvokeRequired)
return value.Func();
return (TRusult)Form.ActiveForm.Invoke(value.Func);
;
private class InvokeFuncImpl<TArg1, TRusult>
public Func<TArg1, TRusult> Func get;
public InvokeFuncImpl(Func<TArg1, TRusult> f)
Func = f;
public static implicit operator Func<TArg1, TRusult>(InvokeFuncImpl<TArg1, TRusult> value)
return (arg) =>
if(Form.ActiveForm == null)
return value.Func(arg);
if(!Form.ActiveForm.InvokeRequired)
return value.Func(arg);
return (TRusult)Form.ActiveForm.Invoke(value.Func, arg);
;
public static Func<TResult> Bulid<TResult>(Func<TResult> f) => new InvokeFuncImpl<TResult>(f);
public static Func<TArg1, TResult> Bulid<TArg1, TResult>(Func<TArg1, TResult> f) => new InvokeFuncImpl<TArg1, TResult>(f);
不幸的是,在 C# 中没有可变参数泛型参数,所以你应该隐式地使所有泛型重载。
所以你可以写这样的代码:
_f = InvokeFunc.Bulid((bool x) =>
textBox1.Multiline = x;
return textBox1.Text.Length;
);
其中 _f 是 Func 类型的某个字段,并且可以在任何线程中安全调用。在实现中,我检查了调用要求,所以如果不需要,则直接调用。
为了行动<...>:
public static class InvokeAction
private class InvokeActionImpl
public Action Action get;
public InvokeActionImpl(Action a)
Action = a;
public static implicit operator Action(InvokeActionImpl value)
return () =>
if(Form.ActiveForm == null)
value.Action();
else if(!Form.ActiveForm.InvokeRequired)
value.Action();
else
Form.ActiveForm.Invoke(value.Action);
;
private class InvokeActionImpl<TArg1>
public Action<TArg1> Action get;
public InvokeActionImpl(Action<TArg1> a)
Action = a;
public static implicit operator Action<TArg1>(InvokeActionImpl<TArg1> value)
return (arg) =>
if(Form.ActiveForm == null)
value.Action(arg);
else if(!Form.ActiveForm.InvokeRequired)
value.Action(arg);
else
Form.ActiveForm.Invoke(value.Action, arg);
;
public static Action Bulid(Action a) => new InvokeActionImpl(a);
public static Action<TArg1> Bulid<TArg1>(Action<TArg1> a) => new InvokeActionImpl<TArg1>(a);
【讨论】:
以上是关于类型安全的 Control.Invoke C#的主要内容,如果未能解决你的问题,请参考以下文章
Control.Invoke() 与 Control.BeginInvoke() [重复]
Control.Invoke() 与其委托的调用之间的延迟是多长时间?