问题
I am programming a software in C# at work that contains 2 Threads
- a Thread that control a Form (Windows Forms) and interfaces with the user.
- 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.
回答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.
回答2:
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);
}
来源:https://stackoverflow.com/questions/59710073/type-safe-control-invoke-c-sharp