原文地址:http://www.cnblogs.com/TankXiao/p/3348292.html#backgroudworker
private void button4_Click(object sender, EventArgs e)
{
using (BackgroundWorker bw = new BackgroundWorker())
{
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerAsync("Tank");
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
// 这里是后台线程, 是在另一个线程上完成的
// 这里是真正做事的工作线程
// 可以在这里做一些费时的,复杂的操作
Thread.Sleep(5000);
e.Result = e.Argument + "工作线程完成";
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//这时后台线程已经完成,并返回了主线程,所以可以直接使用UI控件了
this.label4.Text = e.Result.ToString();
}
我做了这样一个实验
button4_Click此代码是在某子窗体中执行
using (BackgroundWorker bw = new BackgroundWorker())
{
.....
} //将子窗体完全关闭
this.Close();
this.Dispose();
bw_DoWork在此后台函数内什么也不做只是Sleep(5000),休眠了5000毫秒
RunWorkerCompleted在此函数内再次添加
this.Close();[/code]
this.Dispose();
照我抄的这段代码内的注释,RunWorkerCompleted此函数回到了主线程,
可是主线程(子窗体)的代码内,我将其自身已经完全关闭,并且释放,理论上,是否此处因该有异常访问?访问了已经释放的空间?
难道仅是我运气好,程序在RunWorkerCompleted内没有崩溃?
------解决思路----------------------
this.Close();
窗体关闭,但是并不代表实例被销毁
this.Dispose();
告诉GC,该对象可以销毁,但是不代表会立即被回收
既然窗体里还有对象在保持对窗体的强引用,窗体就不会被回收
------解决思路----------------------
跟操作系统销毁对象占用的内存空间是完全不相关的 -> 跟.net底层运行时系统销毁对象占用的内存空间是完全不相关的
真正去销毁空间那是GC的事儿。许多人把 Dispose 误认为成是GC操作了。
------解决思路----------------------
访问this对象的直系成员一般不会报错,比如this.datagridview
但是如果你将this.Close()改成this.datagridview.Rows.Add(1, "abc");多半会报错
为什么说多半?因为这是机率事件,与控件内存实际释放的时机有关(由GC控制)
而this.Close()是不会的,因为它里面调用的是Dispose(bool isDisposing),实际上只会标记需要回收,多次调用没有意义
RunWorkerCompleted是事件(委托),由UI线程来调用的。
从windows消息循环的角度来讲,当窗体关闭时,实际上调用了PostQuitMessage(发送了WM_DESTROY消息),来终止此窗体上的消息循环,你对this的所有操作几乎都离不开它,它停了,你的一切调用也就不再有机会得到执行
另外,如果该窗体是主窗体,this.Close()也就终止了此程序的前台主线程,程序也就退出了(无视BackgroundWorker后台线程,也就是说,还没等你sleep(5000)完,程序就退出了
------解决思路----------------------
由此可见窗体并没有真正被释放, 所以才不会有任何异常. 研究了下 BackgroundWorker 这个类,执行完成事件的代码好复杂都看晕了.
------解决思路----------------------
.Net能保证,但前提是,你是在构造函数中new的对象,并且添加到控件或者组件窗器中,如
this.Controls.Add(ctrl);
var timer = new Timer(this.componet); 当Form中没有使用组件时,你会发现componet并不会初始化
这两种在窗体关闭时都能释放,但有一种必须要你自己释放,比如你点个按钮,然后在代码中new timer()需要你显式调用Dispose来释放,这主要是因为timer使用到了非托管资源(一部分内存不是由CLR和GC来管理,而是像C++那样直接向系统申请的内存)
一个全局引用的sqlConnection,在dispose()后,会不会出现异常?只能说在一个很短的时候内不会,还是跟上面说的一样,跟GC释放它的时机有关。一旦释放后,不管是当前线程还是别的线程访问它都将是空引用。
MSDN的建议是不要使用dispose()过的对象,一旦你用了,就意味着你已经明确表示不需要再使用它了
GC回收是有策略的,回收的时候默认情况下挂起其他的线程(比较影响性能),所以回收不会是实时的。你可以去查查资料
------解决思路----------------------
个人觉得你这两个例子并没有本质区别,都是“不安全”的
beginInvoke用的托管线程池线程(后台线程),只有当所有的前台线程都终止,后台线程才会自动终止
所以要看你的this窗体是否就是主窗体,也就是说,this.Close()之后程序是否退出了?
一旦你用了Dispose(),就意味着你已经明确表示不需要再使用它了,在此之后的任何地方你都不应该再去访问此对象
想想C++中你free过后,会再次使用那个对象吗?(在不重新赋值的前提下)
只要你记住这个原则就行
BackgroundWorker的另一个好处是,还封装了取消机制
通过调用asyncCancel()来取消先前的异步操作,你只需要在RunWorkerCompleted事件中,判断
if(e.Cancelled)
{
return;
}