C# 类型安全控件。调用C

C# 类型安全控件。调用C,c#,multithreading,delegates,type-safety,C#,Multithreading,Delegates,Type Safety,我正在用C语言编写一个包含2个线程的软件 控制窗体的线程Windows窗体和与用户的接口。 在后台检查联机数据的线程。 当在线数据不规则时,我需要第二个线程在表单上打印按摩 因为只有创建控件的线程才能更改它,所以我使用委托。 第二个线程调用第一个线程以通过Control.Invoke方法执行委托 例如: public partial class Form1 : Form { public delegate void SafeCallDelegate(string text);

我正在用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);
    }
}
我可以接受这个不太干净的解决方案,但我的问题是,这根本不是类型安全的

Invoke函数接受一个对象数组,而不管委托需要什么,这可能会导致运行时异常

是否有一种更安全的方法可以解决我的问题


谢谢。

据我所知,您希望生成符合控制调用要求的操作或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);
}
其中_f是任何线程中Func和safe-call类型的字段。在实现中,我检查了invoke需求,所以如果不需要,就直接调用

以及采取行动:


不要传递要调用的参数,而是将它们作为委托中的参数传递

因此,与此相反:

Form.ActiveForm.Invoke(Form1.d, new object[] { "SecondThreadFunc counter: " + counter.ToString() });
写下:

Form.ActiveForm.Invoke
(
    new Action
    (
        () => Form1.d("SecondThreadFunc counter: " + counter.ToString())
    )
);
这避免了传递参数以调用的问题,并消除了类型安全问题

如果您觉得这有点冗长,还可以定义一个简单的助手扩展方法:

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。

这不是解决方案,而是我通常所做的。创建一个可以从任何线程调用的函数,但能够执行UI线程特定的工作。使用Control.InvokeRequired测试调用方是否不在UI线程上。如果是这样,则有效地递归调用相同的函数。这样,非类型安全代码就在一个地方,就在调用要到达的地方。对不起,我在打电话,但这是一个很常见的模式,你应该可以找到例子。是的,我知道这个解决方案,这是我试图避免的解决方案。我只是发现ifControl.InvokeRequired解决方案一点也不优雅。我觉得奇怪的是,如此简单,我认为非常常见的情况没有任何优雅的解决方案。编写正确的异步代码,只需从主线程等待……您可以潜在地构建一个lambda,在中调用您的委托具有表达式树的运行时?
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()));