我试图从一个单独的线程使用this.Invoke()来访问我的表单上的控件。我正在调用一个委托,该委托指向一个以字符串[]作为参数的方法。
关于我的代表声明的几行:
public delegate void delVoidStringArray(string[] s);
public delVoidStringArray _dLoadUserSelect = null;
_dLoadUserSelect = LoadUsers;
从单独的线程调用委托:
Invoke(_dLoadUserSelect, sUsernames);
该方法被调用以处理表单上的控件
private void LoadUsers(string[] users)
{
//Load the list of users into a ListBox
lstUsers.Items.AddRange(users);
//Load the state of a CheckBox on the form
chkUserAlways.Checked = Properties.Settings.Default.PreferDefaultUser;
}
这通常与我的其他代理一起使用各种参数(字符串,控制,表单和无参数),但每当我调用此Invoke()行时,我都会收到错误:“参数计数不匹配。”
我认为发生的事情是我的字符串数组被装入一个对象数组,并且委托试图将这些字符串作为单独的参数传递给该方法。因此,如果字符串数组中有“Bob”“Sally”和“Joe”,则它会尝试将LoadUsers调用为
LoadUsers("Bob", "Sally", "Joe");
显然与签名不符。
这听起来像是可能发生的事吗?我怎么能解决这个问题?
答案 0 :(得分:5)
假设sUsernames
是string[]
,那么是,您需要使用
Invoke(_dLoadUserSelect, new object[] { sUsernames });
.Net数组是协变的,所以这个赋值是有效的:
string[] sUsernames = new[] { "a", "b", "c" };
object[] objs = sUsernames;
当使用params参数调用方法时,数组直接传递,而不是作为参数数组中的第一个元素传递。您需要手动为Invoke
创建参数数组,以获得您期望的行为。
答案 1 :(得分:0)
以下更改将解决问题(方法需要驻留在Form
类中):
internal void LoadUsers(params string[] users)
{
System.Action act = () =>
{
//Load the list of users into a ListBox
lstUsers.Items.AddRange(users);
//Load the state of a CheckBox on the form
chkUserAlways.Checked = Properties.Settings.Default.PreferDefaultUser;
});
this.Invoke(act);
}
如果从表单外部调用,则方法LoadUsers
至少应为internal
,而不是private
。
由于我已将其封装在动作act
中,因此现在可以通过this.Invoke(act);
调用它。
现在,您可以在长时间运行的线程或任务上下文中安全调用LoadUsers
,例如
private void ShowUsers_Click(object sender, EventArgs e)
{
Task.Run(() =>
{ // long running task (e.g. database query running 20 seconds)
Thread.Sleep(20000); // wait 20 seconds
// populate the user's list
string[] sUsernames = new[] { "Bob", "Sally", "Joe" };
LoadUsers(sUsernames);
// or, passed as params: LoadUsers("Bob", "Sally", "Joe");
});
}
在此示例中,作为任务立即在click事件中运行操作可防止表单冻结并显示“未响应...”,因为事件只是对任务的处理,并在任务继续单独运行时立即退出
NB:
act
在这里从技术上讲是作为委托使用的,但是使用Lamba语法,声明(和理解)要容易得多。而且它节省了一些实现工作,因为您不需要先声明一个委托类型然后再使用它。params
是可选的,但是如果您直接传递参数,它将简化调用LoadUsers
的过程。您仍然可以根据需要传递数组。lstUsers
),则可以像lstUsers.Invoke(act);
一样在控件上调用它。在这里这是不可能的,但值得一提。