类型安全 Control.Invoke C#
type-safe Control.Invoke C#
我正在工作中使用 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 函数采用对象数组,无论委托需要什么,这可能会导致 运行 时间异常。
有没有类型安全性更高的方法可以解决我的问题?
谢谢。
据我了解,您希望构建符合 Control Invoke 要求的 Action<...> 或 Func<...> 委托。如果是这样,您可以像这样装饰现有的通用委托:
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);
}
不是将参数传递给 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。
我正在工作中使用 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 函数采用对象数组,无论委托需要什么,这可能会导致 运行 时间异常。
有没有类型安全性更高的方法可以解决我的问题?
谢谢。
据我了解,您希望构建符合 Control Invoke 要求的 Action<...> 或 Func<...> 委托。如果是这样,您可以像这样装饰现有的通用委托:
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);
}
不是将参数传递给 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。