在WinForm项目中,我们通常会采用界面逻辑分离,由逻辑线程触发的对界面控件的操作,会因为跨线程导致各种问题,那么是否有一种通用操作,来解决我们在日常开发遇到的这种问题呢?
我所遇到的具体问题如下:当前有两条线程,线程1为UI线程,所有UI都在此线程中初始化,线程2为消息线程,通常情况下线程2是阻塞的,但是我们必须要在线程2中触发对界面的操作,比如登录结束后,收到login_res,需要隐藏登录界面并且展示主界面。当我在线程2中操纵界面显示的时候,会由于线程2自己的阻塞导致主界面加载到一半便会卡死。此时做线程输出,会发现对界面的操作一直是在线程2上的,即使我强制调用界面的refresh方法刷新界面,也会由于线程的阻塞导致后续界面一直是卡死状态
理所当然的,我们会想到Contral的Invoke。那么接下来,结合我的框架聊一下我的设计。
我的架构采用界面-界面控制-逻辑三层,其中界面控制层提供一系列的委托,提供给逻辑层调用
首先由于我使用的界面并不多,这里我们以登录界面和创角界面来做展示,运行逻辑为:程序开始时创建登录界面,玩家输入账号密码,点击确认后向服务器发送login_req,当服务器检测到没有此账号,便会在login_res中携带一个标识,当消息线程读取到此标识后,会打开创角界面。
下面是登录界面的构造函数
public LoginForm()//登录界面,于程序开始时初始化
{
InitializeComponent();//界面初始化
if (MyClient.SFlag == 1)//对与服务器的连接状体做输出
{
this.label_connect.Text = "连接状态:已连接";
}
Control.CheckForIllegalCrossThreadCalls = false;//允许其他线程操作界面
//在这里初始化界面,此时是在UI线程中初始化
GameFrame.formContral.hideLogin = new FormDelegate(hideLogin);
CreatForm cf = new CreatForm();
//这一步很关键,需要调用界面Show方法,才可以在当前线程创建界面句柄
cf.Show();
cf.Hide();
}
下面是创角界面的构造函数
public CreatForm()
{
InitializeComponent();//界面初始化
Control.CheckForIllegalCrossThreadCalls = false;//允许跨线程访问(可以不加)
GameFrame.formContral.showCreate = new FormDelegate(showForm);//将构该界面中对界面的操作封装成方法交给委托
}
下面是创角界面在消息线程上被调用的方法
public void showForm()
{
OpeMainFormControl(delegate ()
{
this.Show();
});
}
private void OpeMainFormControl(Action action)
{
if (this.InvokeRequired)
{
this.Invoke(action); //返回主线程(创建控件的线程)
}
else
{
action();
}
}
}
首先,这里需要用到控件的Invoke,它可以将对控件的操作委托回控件创建的线程进行操作,这样就解决了跨线程调用的线程安全问题,并且,这个方法是在消息线程中调用,消息线程总是阻塞的。
这里有一点需要特别注意,如果在界面创建的线程上没有调用界面的Show方法,那么是不会在改线程上创建界面的句柄,那么"this.InvokeRequired"的值为false,此时无法invoke!