Search code examples
c#multithreadingdelegatestype-safety

type-safe Control.Invoke C#


I am programming a software in C# at work that contains 2 Threads

  1. a Thread that control a Form (Windows Forms) and interfaces with the user.
  2. a Thread that checks online data at the background.

I need the second thread to print a massage on the form when the online data is irregular.

because only the thread that created the control can change it, I am using delegates. the second thread calls the first thread to execute a delegate by the Control.Invoke method.

Example:

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

I can live with this not very clean solution, but my problem is that this is not type-safe at all.

the Control.Invoke function takes an array of objects, regardless of what the delegate requires and this can result in a run-time exception.

Is there a method to use that is more type-safe and can solve my problem?

Thank you.


Solution

  • Instead of passing the arguments to Invoke, pass them as a closured variable within the delegate.

    So, instead of this:

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

    Write this:

    Form.ActiveForm.Invoke
    (
        new Action
        (
            () => Form1.d("SecondThreadFunc counter: " + counter.ToString())
        )
    );
    

    This avoids the problem of passing arguments to Invoke and eliminates the type-safety issue.

    If that seems a little wordy to you, you can also define a simple helper extension method:

    static class Helpers
    {
        static public void MyInvoke(this Control control, Action action)
        {
            if (control.InvokeRequired)
            {
                control.Invoke(action);
            }
            else
            {
                action();
            }
        }
    }
    

    Now all you have to write in your main form is this:

    this.MyInvoke( () => addText("SecondThreadFunc counter: " + counter.ToString()));
    

    You can now get rid of all that SafeCallDelegate stuff and just pass whatever lambda you want when you need it.