以下是学习笔记,感谢原作者的分享
https://blog.csdn.net/songkexin/article/details/6178540
https://www.jb51.net/article/138586.htm
在 WinForms 中,有时要执行耗时的操作,在该操作未完成之前操作用户界面,会导致用户界面停止响应。解决的方法就是新开一个线程,把耗时的操作放到线程中执行,这样就可以在用户界面上进行其它操作。
新建线程可以用 Thread 类,可以实现多线程同时操作,简单的可以通过 BackgroundWorker 类实现。
BackgroundWorker 类允许您在单独的专用线程上运行操作。 耗时的操作(如下载和数据库事务)在长时间运行时可能会导致用户界面 (UI) 似乎处于停止响应状态。 如果您需要能进行响应的用户界面,而且面临与这类操作相关的长时间延迟,则可以使用
BackgroundWorker 类方便地解决问题。
常用方法:
1.RunWorkerAsync
开始执行后台操作。引发 DoWork 事件
2.CancelAsync
请求取消挂起的后台操作。
注意:这个方法是将 CancellationPending 属性设置为 true,并不会终止后台操作。在后台操作中要检查 CancellationPending 属性,来决定是否要继续执行耗时的操作。
3.ReportProgress
引发 ProgressChanged 事件。
常用属性:
1.CancellationPending
指示应用程序是否已请求取消后台操作。
只读属性,默认为 false,当执行了 CancelAsync 方法后,值为 true。
2.WorkerSupportsCancellation
指示是否支持异步取消。要执行 CancelAsync 方法,需要先设置该属性为 true。
3.WorkerReportsProgress
指示是否能报告进度。要执行 ReportProgress 方法,需要先设置该属性为 true。
常用事件:
1.DoWork
调用 RunWorkerAsync 方法时发生。
2.RunWorkerCompleted
后台操作已完成、被取消或引发异常时发生。
3.ProgressChanged
调用 ReportProgress 方法时发生。
在 DoWork 事件处理程序中不操作任何用户界面对象。而应该通过 ProgressChanged 和 RunWorkerCompleted 事件与用户界面进行通信。
如果想在 DoWork 事件处理程序中和用户界面的控件通信,可在用 ReportProgress 方法。
ReportProgress(int percentProgress, object userState),可以传递一个对象。
ProgressChanged 事件可以从参数 ProgressChangedEventArgs 类的 UserState 属性得到这个信息对象。
简单的程序用 BackgroundWorker 比 Thread 方便,Thread 中和用户界面上的控件通信比较麻烦,需要用委托来调用控件的 Invoke 或 BeginInvoke 方法,没有 BackgroundWorker 方便。
有2点需要注意的:
1、由于DoWork事件内部的代码运行在非UI线程之上,确保在 DoWork 事件处理程序中不操作任何用户界面对象。 而应该通过 ProgressChanged 和 RunWorkerCompleted 事件与用户界面进行通信。
2、BackgroundWorker 事件不跨 AppDomain 边界进行封送处理。 请不要使用 BackgroundWorker 组件在多个 AppDomain 中执行多线程操作。
实例应用:查询数据库的数据用Chart展示
【1】BackgroundWorker使用步骤【1】:声明一个BackgroundWorker
//BackgroundWorker使用步骤【1】:声明一个BackgroundWorker BackgroundWorker backgroundWorker = new BackgroundWorker();
【2】BackgroundWorker使用步骤【2】:绑定事件
窗体加载的时候
//BackgroundWorker使用步骤【2】:绑定事件 backgroundWorker.DoWork += BackgroundWorkerDowork; backgroundWorker.RunWorkerCompleted += BackgroundWorkerRunWorkerCompleted;
【3】BackgroundWorker使用步骤【3】:Dowork事件(调用 RunWorkerAsync 方法时发生。)
//BackgroundWorker使用步骤【3】:Dowork事件(调用 RunWorkerAsync 方法时发生。) private void BackgroundWorkerDowork(object sender, DoWorkEventArgs e) { //DoWork事件处理函数通过参数 e 的Argument属性获取参数 List<DateTime> timelist = (List<DateTime>)e.Argument;//异步执行的参数,因为已经知道是List<DateTime>类型,所以强制转换 DataTable dt = new DataTable(); string sql = "select * from mydata where FDateTime >=" + "format('" + timelist[0].ToString() + "')" + "and FDateTime <=" + "format('" + timelist[1].ToString() + "')" + "and PsetName =" + "'" + psetNo + "'" + "order by ID"; dt = this.myaccess.SelectToDataTable(sql); e.Result = dt; }
【4】BackgroundWorker使用步骤【4】:RunWorkerCompleted事件(当DoWork事件处理完成之后,将会触发该事件)
//BackgroundWorker使用步骤【4】:RunWorkerCompleted事件(当DoWork事件处理完成之后,将会触发该事件) private void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { DataTable dt = (DataTable) e.Result;//e.Result 获取异步操作结果的值,即DoWork事件中,Result设置的值 dtSearchData = dt; #region 拧紧趋势曲线显示 #region 测试1:没有解决X轴时间不连续,X轴刻度不均分的问题 //this.chart3.DataSource = dt; //this.chart3.Series["CPK"].XValueMember = "FDateTime";//这个FDateTime是时间格式,X轴刻度不是均分的 ////this.chart1.Series["实时趋势"].XValueMember = "FAngle";//这个FAngle不是时间格式的,X轴刻度是等间距的 //this.chart3.Series["CPK"].YValueMembers = "FTorque"; #endregion #region 测试2:没有解决X轴时间不连续,X轴刻度不均分的问题 //for (int i = 0; i < dt.Rows.Count; i++) //{ // chart3.Series[0].Points.AddXY(dt.Rows[i]["FDateTime"], dt.Rows[i]["FTorque"]); //} #endregion #region 测试3:解决X轴时间不连续,X轴刻度不均分的问题 for (int i = 0; i < dt.Rows.Count; i++) { chart1.Series[0].Points.AddXY(dt.Rows[i]["FDateTime"].ToString(), dt.Rows[i]["FTorque"].ToString()); } #endregion for (int i = 0; i < dt.Rows.Count; i++) { chart2.Series[0].Points.AddXY(dt.Rows[i]["FDateTime"].ToString(), dt.Rows[i]["FTorque"].ToString()); } #endregion #region CPK计算显示 List<float> floatList = new List<float>(); for (int i = 1; i < dt.Rows.Count; i++) { string strTorque = dt.Rows[i]["FTorque"].ToString(); //没有包含中文字符 if (!StringHelper.HasChinese(strTorque)) //如果没有中文 { floatList.Add(Convert.ToSingle(strTorque)); } } float[] floatArray = new float[floatList.Count]; floatArray = floatList.ToArray(); try { this.txt_torqueMax.Text = dt.Rows[0]["FTorqueMax"].ToString();//如果dt没收数据,这里会报错 this.txt_torqueMin.Text = dt.Rows[0]["FTorqueMin"].ToString(); } catch (Exception) { MessageBox.Show("当前程序号没有数据"); } try { UpperLimit = Convert.ToSingle(this.txt_torqueMax.Text.ToString()); LowerLimit = Convert.ToSingle(this.txt_torqueMin.Text.ToString()); } catch { return; } float f = new CpkHepler().getCPK(floatArray, UpperLimit, LowerLimit); this.lbl_cpk1.Text = f.ToString(); #endregion }
【5】BackgroundWorker使用步骤【5】:启动worker异步操作
按钮点击事件
//查询按钮 开始查询 private void btn_query_Click(object sender, EventArgs e) { psetNo = this.cmb_psetNo.Text.Trim(); this.cmb_trendType.SelectedIndex = 1;// this.timer1.Enabled = false;//停止timer1的实时更新 this.btn_Update.Text = "开始更新"; DateTime t1 = Convert.ToDateTime(this.dtp_start.Text); DateTime t2 = Convert.ToDateTime(this.dtp_end.Text); if (t1 > t2) { MessageBox.Show("开始时间必须大于结束时间", "查询提示"); return; } TimeSpan ts = t2 - t1; if (ts.TotalHours > 6) { MessageBox.Show("查询范围太多", "查询提示"); return; } List<DateTime> time = new List<DateTime>(){t1,t2}; //BackgroundWorker使用步骤【5】:启动worker异步操作 if (!backgroundWorker.IsBusy)//如果BackgroundWorker使用步骤不在异步运行 { backgroundWorker.RunWorkerAsync(time);//RunWorkerAsync方法让worker开始工作,调用DoWork方法 } }
【6】BackgroundWorker使用步骤【6】:取消操作
窗体关闭的时候
private void btn_close_Click(object sender, EventArgs e) { //BackgroundWorker使用步骤【6】:取消操作 if (backgroundWorker.WorkerSupportsCancellation == true) { backgroundWorker.CancelAsync();//请求取消挂起的后台操作。调用该方法将使BackgroundWorker.CancellationPending属性设置为True。 //backgroundWorker.CancelAsync()并不会终止后台操作。在后台操作中要检查 CancellationPending 属性,来决定是否要继续执行耗时的操作。 } this.Dispose(); }
Chart控件初始化:
private void InitChart() { chart1.Series.Clear(); ChartHelper.AddSeries(chart1, "曲线图", SeriesChartType.Line, Color.FromArgb(100, 46, 199, 201), Color.White, true,true); ChartHelper.SetTitle(chart1, "工具1", new Font("微软雅黑", 12), Docking.Bottom, Color.FromArgb(46, 199, 201)); ChartHelper.SetStyle(chart1, Color.Transparent, Color.White); ChartHelper.SetLegend(chart1, Docking.Top, StringAlignment.Center, Color.Transparent, Color.White); ChartHelper.SetXY(chart1, "时间", "扭矩.Nm", StringAlignment.Far, Color.White, Color.White, AxisArrowStyle.None); this.chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false; chart1.Series["曲线图"].BorderWidth = 3; chart1.ChartAreas[0].AxisX.Enabled = AxisEnabled.False;//不显示X轴坐标 chart2.Series.Clear(); ChartHelper.AddSeries(chart2, "曲线图", SeriesChartType.Line, Color.FromArgb(100, 46, 199, 201), Color.White, true,true); ChartHelper.SetTitle(chart2, "工具2", new Font("微软雅黑", 12), Docking.Bottom, Color.FromArgb(46, 199, 201)); ChartHelper.SetStyle(chart2, Color.Transparent, Color.White); ChartHelper.SetLegend(chart2, Docking.Top, StringAlignment.Center, Color.Transparent, Color.White); ChartHelper.SetXY(chart2, "时间", "扭矩.Nm", StringAlignment.Far, Color.White, Color.White, AxisArrowStyle.None); this.chart2.ChartAreas[0].AxisX.MajorGrid.Enabled = false; chart2.Series["曲线图"].BorderWidth = 3; chart2.ChartAreas[0].AxisY.IsStartedFromZero = false;//Y轴自适应 chart2.ChartAreas[0].AxisX.Interval = 1; //设置X轴坐标的间隔为1,解决了/X轴间隔1个显示的问题 chart3.Series.Clear(); ChartHelper.AddSeries(chart3, "CPK", SeriesChartType.Line, Color.Lime, Color.FromArgb(109, 89, 71), true, true); ChartHelper.SetStyle(chart3, Color.Transparent, Color.White); ChartHelper.SetLegend(chart3, Docking.Top, StringAlignment.Far, Color.Transparent, Color.FromArgb(100, 128, 206)); ChartHelper.SetXY(chart3, "", "", StringAlignment.Far, Color.FromArgb(100, 128, 206), Color.FromArgb(100, 128, 206), AxisArrowStyle.None); this.chart3.ChartAreas[0].AxisX.MajorGrid.Enabled = false; this.chart3.ChartAreas[0].AxisY.IsStartedFromZero = false;//Y轴自适应 chart3.ChartAreas[0].AxisX.Interval = 1; //设置X轴坐标的间隔为1,解决了/X轴间隔1个显示的问题 }
最终效果: