Безопасный тип Control.Invoke C# - PullRequest
0 голосов
/ 13 января 2020

Я программирую программное обеспечение на C# на работе, которое содержит 2 потока

  1. потока, который управляет формой (Windows форм) и взаимодействует с пользователем.
  2. Поток, который проверяет онлайн-данные в фоновом режиме.

Мне нужен второй поток, чтобы напечатать массаж на форме, когда данные в Интернете нерегулярны.

, потому что только поток, который Созданный элемент управления может изменить его, я использую делегатов. второй поток вызывает первый поток для выполнения делегата методом 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 принимает массив объектов, независимо от того, что требуется делегату, и это может привести к исключению во время выполнения.

Is Есть ли способ использовать более безопасный тип и может решить мою проблему?

Спасибо.

Ответы [ 2 ]

1 голос
/ 14 января 2020

Вместо передачи аргументов Invoke, передайте их как закрытую переменную внутри делегата.

Итак, вместо этого:

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 и просто передать все, что вы хотите лямбда, когда вам это нужно.

0 голосов
/ 13 января 2020

Насколько я понимаю, вы хотите, чтобы сборка Action <...> или Fun c <...> делегировала это требование Control Invoke. Если это так, вы можете украсить существующий делегат generi c следующим образом:

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# нет аргумента variadi c generi c, поэтому вы должны сделать перегрузку всех generi c неявно.

Таким образом, вы можете написать такой код:

_f = InvokeFunc.Bulid((bool x) =>
        {
            textBox1.Multiline = x;
            return textBox1.Text.Length;
        });

Где _f - это поле типа Fun c и безопасный вызов в любом потоке. В реализации я сделал проверку для требования вызова, поэтому, если это не требуется, то сделал прямой вызов.

И для действия <...>:

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);
}
...