转:http://www.cnblogs.com/bluewater/archive/2007/06/13/781708.html
下面说明了五种可以实现等待窗体的方式,其中三种给出了代码。
准备资料
安全访问控件成员
为了保证在创建控件的线程上调用控件成员,用下面的方式封装控件的属性、方法、其他自定义成员的访问。
如: winWordControl.LoadDocument()封装为:
delegate void VoidDelegate();
private void LoadDocument()
{
if (InvokeRequired)
{
Invoke(new VoidDelegate(LoadDocument));
return;
}
this.winWordControl.LoadDocument();
}
下面的代码封装了winWordControl.OpenMode=Opration
private delegate void OpenModeDelegate(Operation op);
private void OpenMode(Operation value)
{
if (InvokeRequired)
{
Invoke(new OpenModeDelegate(OpenMode), new object[] { value });
return;
}
winWordControl.OpenMode = value;
}
这些封装保证了对控件成员的访问在任何线程都是安全的。
例子说明
例子假定有三个窗体:
MainForm:
Preview preview;//Preview 是在此线程创建
Privite void SomeMethod()
{
Thread wordInit = new Thread(new ThreadStart(preview.InitWordDocControl));
wordInit.Start();
}
第二个窗体
PreviewForm:
WinWordControl winWordControl = new WinWordControl();
Private void InitWordDocControl()
{
//执行初始化加载word、实现延迟窗体
//TODO
}
第三个窗体
WaitForm:提示信息
实现方式
InitWordDocControl为了在加载word时不出现假死,必须开启新线程。
由于用到了非托管资源,最简单的方式是把托管资源(WaitForm)放在工作线程,线程结束,窗体会自动销毁,不用自己写清理代码。
第一种:ShowDialog
ShowDialog自动阻塞当前线程,这使它成为最优的解决方式。
Private void InitWordDocControl()
{
Thread thread = new Thread(new ThreadStart(Waiting));
thread.Start();
LoadDocument();
thread.Abort();//销毁线程,自动回收托管资源
}
private void Waiting()
{
//局部变量,在此线程创建,可以直接操作其成员
Wait FormWait = new Wait();
FormWait.StartPosition = FormStartPosition.CenterScreen;
FormWait.ShowDialog(); //线程等待
}
private void LoadDocument()
{
if (InvokeRequired)
{
Invoke(new VoidDelegate(LoadDocument));
return;
}
this.winWordControl.LoadDocument();
}
最简单的解决方式,利用了托管资源的优势和ShowDialog本身的特性。
第二种:Show
如果简单的修改Waiting为:
private void Waiting()
{
Wait FormWait = new Wait();
FormWait.StartPosition = FormStartPosition.CenterScreen;
FormWait.Show(); //窗体立即被销毁
}
窗体肯定会一闪而过,因为FormWait是局部变量,出了方法体就会被回收。
因此要改成下面的形式:
首先把局部变量改为字段,让他在加载类型时分配内存。其次,做如此修改后,创建Wait的线程就变成了创建Preview的线程,这样就不能直接修改此窗体属性,必须用Invoke。
private void Waiting()
{
//下面的调用都是线程安全的,内部都会判断是否是创建线程,不是会调用Invoke
CreateWait();
SetFormStartPosition(FormStartPosition.CenterScreen);
ShowWait();
}
修改:
Private void InitWordDocControl()
{
Thread thread = new Thread(new ThreadStart(Waiting));
thread.Start();
thread.Join();//阻塞调用线程,让其先执行完show
LoadDocument();
}
修改LoadDocument()
private void LoadDocument()
{
if (InvokeRequired)
{
Invoke(new VoidDelegate(LoadDocument));
return;
}
this.winWordControl.LoadDocument();
CloseWait(); //释放资源,线程自动销毁。注意也要使用线程安全的形式。
}
第三种:异步委托
本质上通过线程池中的线程执行委托方法,仍然是线程问题。但是可以用show和异步委托结合,简单的实现等待。可以看出代码比上面实现简单许多。
首先这次把加载word放到新线程中,而WaitForm在原线程。
修改:
Private void InitWordDocControl()
{
Waiting();
MethodInvoker mi = new MethodInvoker(LoadDocument);
mi.BeginInvoke(AsyncCallClose,null);//执行完委托方法,执行AsyncCallClose来关闭等待窗体。
}
private void AsyncCallClose(IAsyncResult ar)
{
FormWait.Close();
}
MethodInvoker,只是系统定义的委托,提供些许便利,更好的方式是自己定义如:
delegate void VoidDelegate();VoidDelegate 和MethodInvoker是等价的。
在这种实现中发现了下面的问题:控件成员可以不在创建控件的线程中使用!!如FormWait.Close();调用此语句的是线程池中的线程,而FormWait是在另外的线程中创建。不知道是什么原因??按照必须在创建控件线程调用成员,则会抛异常。按照许多资料,这种情况也是非法调用,但在此却没有问题?!
第四种:信号量
以前没有用过线程,遇到这个问题,首先想到的是信号量来控制线程通信。可以参考后面的资料或者我以前的文章:
http://www.cnblogs.com/bluewater/archive/2006/08/14/476720.html
第五种:Timer
本质也是线程,有同学说,他一直在VB中用Timer来模拟线程来控制时间片。应该也能实现等待窗体。
在此使用线程,主要是为了有好的用户体验,避免假死。
第一次使用线程,查了许多资料,但仍然有个问题没有解决,再写一遍,希望有人指点原因。
在这种实现中发现了下面的问题:控件成员可以不在创建控件的线程中使用!!如FormWait.Close();调用此语句的是线程池中的线程,而FormWait是在另外的线程中创建。不知道是什么原因??按照必须在创建控件线程调用成员,则会抛异常。按照许多资料,这种情况也是非法调用,但在此却没有问题?!
资料:
一系列关于线程的入门文章,非常好
http://www.yoda.arachsys.com/csharp/threads/parameters.shtml
关于UI
http://msdn.microsoft.com/msdnmag/issues/03/02/Multithreading/
关于Timer
http://msdn.microsoft.com/msdnmag/issues/04/02/TimersinNET/default.aspx