0

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.

Yakir Shlezinger
  • 147
  • 1
  • 13
  • Not a solution, but what I normally do. Create a function that can be called from any thread, but is capable of doing the UI thread-specific work. Use `Control.InvokeRequired` to test if the caller is not on the UI thread. If so, Invoke the same function (effectively recursively). That way, the non-type-safe code is in one place, right at where the call is going to land. Sorry, I'm on a phone, but this is a pretty common pattern, you should be able to find examples – Flydog57 Jan 13 '20 at 03:14
  • Yes, I know this solution,this is the solution that i am trying to avoid.I just find the if(Control.InvokeRequired) solution not elegant at all.I find this odd that such a simple (and i think pretty common situation) don't have any elegant solution. – Yakir Shlezinger Jan 13 '20 at 03:18
  • Write correct async code and simply `await` from main thread... – Alexei Levenkov Jan 13 '20 at 03:25
  • You could potentially build a lambda that invokes your delegate in runtime with expression trees? – timur Jan 13 '20 at 03:40

2 Answers2

1

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.

John Wu
  • 50,556
  • 8
  • 44
  • 80
0

As I understand you want build Action<...> or Func<...> delegate that respect Control Invoke requirement. If so you can Decorate existing generic delegate like so:

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

Unfortunately in C# there not variadic generic argument, so you should make all generic overload implicitly.

So you may write such code:

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

Where _f is some field of type Func and safe call in any thread. In implementation I made check for invoke requirement, so if it don't required then making direct call.

And for Action<...>:

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