本人没做过基于WPF的应用程序,对数据驱动不太了解,没具体做过,经常做的一些是基于Winform的应用程序,在很多即时操作场景当中,对于多线程操作,大多数都一知半解,我也一样,但通过这些年的总结学习和使用,总结出一套使用多线程与窗体异步交互的操作。
多线程有多种操作方式,如Winform自带的BackgroundWorker控件,但对于真正开发时,本人使用的时候是非常少的,原因在于使用起来麻烦不说,还容易出错。再一个就是使用Thread函数,来自定义线程操作,我们可以将窗体看成是一个整体,线程代表各自执行的任务,每个任务执行完成后,告诉窗体如何显示,在Winform中,我喜欢在线程当中,使用事件触发的方式,来将信息反馈给窗体,这样既可以保证代码的整体逻辑晰,阅读性高,还可使线程中的各部分分工明确,即使一部分出问题,不影响整体。
那么来说一说多线程的交互方式,在framework2.0以后,使用多线程时,要么使用CheckForIllegalCrossThreadCalls忽略线程错误,这样可以将数据直接反馈给窗体,缺点就是,当数据交互比较频繁时,容易出现数据冲突或内存错误,导致窗体显示部分的崩溃。第二,是使用delegate来交替实现,缺点是容易出现混乱,并且对于各部分操作来讲,每操作一个控件,写一堆委托,太麻烦。framework 3.0以后,出现了一个action,这个是一个简易的委托,可以减少委托的使用,太不可避免有访问不到窗体线程的时候,这时候需要一个控件的RequiadInvoke来判断,或者直接不判断,使用this.Invoke来直接异步,简单粗爆,但不可避免的还是会出现是否需要异步的操作,使程序崩溃,最后一个是使用AsyncOperation来实现,这个是异步操作管理器,我现在基本上是使用这个加Thread来实现整个窗体界面的线程与窗体之间的异步交互。以前上是我所使用过的所有线程操作方式,对比来讲,最后一个是最好的,前面的如果在操作频繁的程序当中,容易产生大量的Handle句柄,当超出65535个的时候,程序必然会崩溃,原因就是在程序执行线程过程当中,系统每生成一次线程,就会产生一个Handle,但当我们使用完成后,系统不会立即收回,而是在一定时间之后,才会收回。而AsyncOperation是可以告诉系统,所有线程使用的异步操作,立即可以收回。
那么,如何使用AsyncOperation呢?当然,网上百度搜索一下,可以找出一堆的用法,但结合整体的程序,而不出问题的,基本没有,怎么使用,需要我们自己具体问题具体分析,而我在使用时,结合总结,使AsyncOperation可以基本上满足各种需求,举个例子:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_click(object sender, EventArgs e)
{
Label1.text = string.Empty;
for(int i = 0;i<1000;i++)
{
Label1.text += i.ToString() + "\r\n";
}
}
}
这是一个简单的窗体程序,窗体上有控件Button和控件Label,Label负责显示信息,Button单击时,执行0~1000行的信息显示。不使用线程,程序执行时,感觉像是卡死了。那么来改一下:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(Start));
thread.Start();
}
private void Start()
{
Label1.text = string.Empty;
for(int i = 0;i<1000;i++)
{
this.Invoke(new Action(()=>{
Label1.text += i.ToString() + "\r\n";
}));
}
}
}
这时候程序运行的时候,打开任务管理器,会发现窗体句柄一直在涨,很难下去,那么我们再来改一下:
public partial class Form1 : Form
{
AsyncOperation _AsyncOperation;
public Form1()
{
InitializeComponent();
_AsyncOperation = AsyncOperationManager.CreateOperation(null);
}
private void button1_click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(Start));
thread.Start();
}
private void Start()
{
Label1.text = string.Empty;
for(int i = 0;i<1000;i++)
{
_AsyncOperation.Post(new System.Threading.SendOrPostCallback(delegate(object state) {
Label1.text += i.ToString() + "\r\n";
}), null);
_AsyncOperation.OperationCompleted();
}
}
}
运行后,发现_AsyncOperation还是报错,原因是第二次调用时,_AsyncOperation已经被释放掉了,不能再被用了,最终修改方式就是:
public partial class Form1 : Form
{
AsyncOperation _AsyncOperation;
public Form1()
{
InitializeComponent();
}
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
if(this.Visible)
{
_AsyncOperation = AsyncOperationManager.CreateOperation(null);
}
else
{
_AsyncOperation.OperationCompleted();
}
}
private void button1_click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(Start));
thread.Start();
}
private void Start()
{
Label1.text = string.Empty;
for(int i = 0;i<1000;i++)
{
_AsyncOperation.Post(new System.Threading.SendOrPostCallback(delegate(object state) {
Label1.text += i.ToString() + "\r\n";
}), null);
}
}
}
对事件委托也一样,对于继续类父窗体,我喜欢使用protected virtual void Onxxx(EventArgs e)这样的写法,原因就是可以在很多情况下,子窗体可以使用override来重写。对于一般的窗体发生的事件,去掉virtual就好,可以把_AsyncOperation调用直接放在这里,这样,不管是窗体调用还是线程调用,只要写共用的方法调用就好,这样,就可以保证窗体间的异步调用不会出问题,并且在任务管理器当中,句柄的增长数非常低,并且释放的也及时,不用担心因柄句而导致程序崩溃。由于写得匆忙,只是记录一下,如有看见的,不要挑毛病,有不懂的可以提问,写的方试问题就不要讨论了。