C# 跨类 跨线程 更新界面
经常要用到C#子线程中更新界面,以前都是要用到了找一找,大部分都是窗体类的子线程去访问,比较少遇到在另外一个类里面去更新窗体。
下面先介绍一下理论知识。参考文章来源:
https://www.cnblogs.com/dzw2017/p/7479477.html
https://www.cnblogs.com/TankXiao/p/3348292.html
https://blog.****.net/kellygod/article/details/75452932
……
在C#应用程序开发中,我们经常需要把UI线程和工作线程分开编程,为了防止界面停止响应。同时,我们也需要在工作线程中去更新UI界面的控件,在CLR的线程安全中并不允许我们直接在工作线程操作UI界面。因此,介绍以下三种方式进行跨线程操作UI。
第一种方法:使用delegate和invoke来从其他线程中调用控件
private void button2_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(new ParameterizedThreadStart(UpdateLabel2));
thread1.Start("更新Label");
}
private void UpdateLabel2(object str)
{
// 当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它
if (label2.InvokeRequired)
{
//lamda表达式的action委托,<string>为参数类型,(x)为形参
Action<string> actionDelegate = (x) => { this.label2.Text = x.ToString(); };
// 或者
// Action<string> actionDelegate = delegate(string txt) { this.label2.Text = txt; };
//同步**委托action
this.label2.Invoke(actionDelegate, str);
}
else
{
this.label2.Text = str.ToString();
}
}
第二种方法:使用delegate和BeginInvoke来从其他线程中控制控件
该方法与上述方法的唯一差别在于,其中的Invoke**函数换成了BeginInvoke**函数。 两个函数的本质区别在于,Invoke方法是线程同步,当工作线程执行完毕后,才会再次触发; 而BeginInvoke方法是线程异步,当工作线程还未执行完,它便会开启另一个线程去完成工作线程。
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 + "工作线程完成";
}
//回到UI主线程
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//这时后台线程已经完成,并返回了主线程,所以可以直接使用UI控件了
this.label4.Text = e.Result.ToString();
}
下面介绍自己所用的方法。
背景:窗体main中,定时执行类DataManipulate中方法BeginWork(),BeginWork()会开辟多个线程执行方法QuerySingleCompany();在QuerySingleCompany中需要更新main中RichTextBox(name:rtblog),添加日志消息文本。
首先窗体类main.cs
#define Test
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
namespace LvWangData
{
public partial class Main : Form
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
DataManipulate dm = new DataManipulate();
public Main()
{
InitializeComponent();
#region 非定时 测试用
#if Debug
//dm = new DataManipulate();
//dm.LogEventHandler += new DataManipulate.LogHandler(Dm_LogEventHandler);
//dm.BeginWork();
#endif
#endregion
}
System.Timers.Timer timer = new System.Timers.Timer();
private void btnQueryOnTime_Click(object sender, EventArgs e)
{
#if Test
BaseDB.Model.LvWangInfo lw = new BaseDB.Model.LvWangInfo();
lw.CompanyID = "E9855A6C-3F1A-4AA0-9AAB-DE3AF2E10031";
lw.IsValid = true;
lw.LastQueryTime = Convert.ToDateTime("2019-02-13 00:00:00.000");
lw.LvwangID = 942277;
DataManipulate dm = new DataManipulate();
dm.LogEventHandler += new DataManipulate.LogHandler(Dm_LogEventHandler);
dm.OpenTestFunc(lw);
#elif Deploy
if (btnQueryOnTime.Text.Equals("开启定时查询"))
{
//System.Timers.Timer timer = new System.Timers.Timer();
timer.Enabled = true;
timer.Interval = 55000;//执行间隔时间,单位为毫秒;此时时间间隔为1分钟
timer.Start();
timer.Elapsed += new System.Timers.ElapsedEventHandler(QueryOnTime);
btnQueryOnTime.Text = "关闭定时查询";
}
else
{
timer.Close();
btnQueryOnTime.Text = "开启定时查询";
}
#endif
}
/// <summary>
/// 每个1小时查询一次
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private void QueryOnTime(object source, ElapsedEventArgs e)
{
int min = Convert.ToInt32(Configs.GetValue("Min"));
if (DateTime.Now.Minute == min)
{
dm = new DataManipulate();
dm.LogEventHandler += new DataManipulate.LogHandler(Dm_LogEventHandler);
BackgroundWorker bgw = new BackgroundWorker();
// 线程执行的内容
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerAsync();
}
}
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
dm.BeginWork();
}
private void Dm_LogEventHandler(string logstr)
{
if (rtblog.InvokeRequired)
{
// 当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它
Action<string> actionDelegate = (x) => { this.rtblog.AppendText(x.ToString() + "\r"); };
// 或者
// Action<string> actionDelegate = delegate(string txt) { this.label2.Text = txt; };
this.rtblog.BeginInvoke(actionDelegate, logstr);
}
else
{
this.rtblog.AppendText(logstr.ToString() + "\r");
}
}
public void UpdateRtbLog(object str)
{
if (rtblog.InvokeRequired)
{
// 当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它
Action<string> actionDelegate = (x) =>
{
if (this.rtblog.TextLength > 102400)
{
this.rtblog.Clear();
}
this.rtblog.AppendText(x.ToString());
};
// 或者
// Action<string> actionDelegate = delegate(string txt) { this.label2.Text = txt; };
this.rtblog.Invoke(actionDelegate, str);
}
else
{
this.rtblog.AppendText(str.ToString());
}
}
}
}
在DataManipulate.cs中,只留下了关键代码
using LvWangData.Entity;
using Maticsoft.DBUtility;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace LvWangData
{
public class DataManipulate
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
public delegate void LogHandler(string logstr);
/// <summary>
/// 定义捕获到socket事件,类型AcceptSocketHandler
/// </summary>
public event LogHandler LogEventHandler;
public DataManipulate()
{
logger.Info("进入DataManipulate");
if (LogEventHandler != null)
{
LogEventHandler("进入DataManipulate");
}
}
/// <summary>
/// 单独分出来,开始工作
/// </summary>
public void BeginWork()
{
Init();
if (LogEventHandler != null)
{
LogEventHandler("BeginWork()");
}
//获取LvWangInfo表中数据,每一家公司开辟一个线程进行数据查询入库
if (_lvwanginfolist != null)
{
foreach (BaseDB.Model.LvWangInfo lw in _lvwanginfolist)
{
Thread.Sleep((new Random()).Next(100, 2000));
Thread t = new Thread(new ParameterizedThreadStart(QuerySingleCompany));
t.Start(lw);
}
}
}
private void Init()
{
try
{
StringBuilder sb = new StringBuilder();
sb.Append(DateTime.Now.ToString() + "获取绿网公司信息:\r");
foreach (BaseDB.Model.LvWangInfo info in _lvwanginfolist)
{
sb.Append(info.CompanyID + "\t" + info.LastQueryTime +"\r");
}
LogEventHandler(sb.ToString());
logger.Info(sb.ToString());
LogEventHandler("Init Over");
}
catch (Exception e)
{
logger.Error("Init\t" + e.StackTrace);
LogEventHandler(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\tInit\t" + e.StackTrace);
}
}
}
}