前言
C#实现窗体加载进度条或者百分比实时显示耗时操作的进度,方法有很多。但是经过我的学习、查找与实际应用,发现Task配合MethodInvoker最为高效便捷。下面我就来结合代码讲一下要注意的问题。
基础知识
C#在winform上进行耗时操作往往会放置progressbar,问题是在UI线程上进行耗时操作就会导致UI线程阻塞,界面就会卡顿。所以势必要另开一个线程进行耗时操作,之后将耗时操作的过程实时反馈给UI线程即可,可问题是新开的线程向UI线程传递数据的时候,就会出现经典报错:
InvalidOperationException,并提示消息:“从不是创建控件的线程访问它。
这是因为NET原则上禁止跨线程访问。因为这样可能造成错误的发生,有一种简单粗暴的方法是禁止编译器对跨线程访问作检查,Control.CheckForIllegalCrossThreadCalls = false;可以实现访问,但是什么时候出错不敢保证。
Task
Task是一个升级版本的Thread的类,它非常的灵活,支持取消、阻塞等待、合并、多个Task协同操作......。总之使用Task编码高效易懂,你基本不用去研究Thread与ThreadPool了,虽然本质上还是这个。我个人理解Task就是对Thread的再次封装。
task
MethodInvoker
MethodInvoker 是位于System.Windows.Forms下的元数据,表示一个委托,该委托可以执行托管代码中声明为void且不接受任何参数的任何方法。在对控件的 invoke 方法进行调用时或需要一个简单委托又不想自己定义时可以使用该委托。 我是这样理解的,在新线程中使用MethodInvoker 委托执行耗时操作, 其实相当于是在主线程中执行的, 这样就避免了 跨线程访问控件
methodinvoker
示例代码
private void button1_Click(object sender, EventArgs e)
{
progressBar1.Visible = true;
Task task = new Task(() =>
{
int i = 0;
while (++i < 100)
{
Thread.Sleep(10);//模拟耗时操作
MethodInvoker mi = new MethodInvoker(() =>
{
progressBar1.Value = i;
this.label1.Text = i.ToString();
});
this.BeginInvoke(mi);
}
});
task.Start();
task.ContinueWith(t => {
progressBar1.Visible = false;
},TaskScheduler.FromCurrentSynchronizationContext());
}
线程的延续采用ContinueWith解决
BeginInvoke解决界面的刷新问题
TaskScheduler.FromCurrentSynchronizationContext() 解决跨线程访问报错
private void button2_Click(object sender, EventArgs e)
{
Task task1 = new Task(() =>
{
M1();
MethodInvoker mi = new MethodInvoker(() =>
{
this.label1.Text = "1";
});
this.BeginInvoke(mi);
M2();
mi = new MethodInvoker(() =>
{
this.label1.Text = "2";
});
this.BeginInvoke(mi);
});
task1.Start();
this.label1.Text="主线程开始运行!" ;
}
private void M1()
{
Thread.Sleep(2000);
}
private void M2()
{
Thread.Sleep(1000);
}
button2的方式可以在task线程中按顺序执行耗时操作。