网站首页 > 技术文章 正文
在WinForm项目中,很多时候会映遇上多线程一起工作的情况,因为当前UI的更新显示,是在主线程中,一但主线程被长时运算占据后,UI就会被卡信,出现假死现像。那么就需要起一个新线程做长时运算工作,把进度或数据同步回UI线程。
以一个医保上传数据为例,功能是同步药品,器械,诊疗项目,同步完后进行验证核对。
注:为了看得清晰,各个关键控件我没有重命名
定义一个list来充当步骤和需要时间。
static List<Item> list;
private void Form1_Load(object sender, EventArgs e)
{
list = new List<Item>
{
new Item{ Name="正在上传诊疗项目",Time=8 },
new Item{ Name="正在上传器材",Time=12 },
new Item{ Name="正在上传药品",Time=20 },
new Item{ Name="正在核对",Time=24 },
};
}
第一版:用Task.Run来启动一个新的线程,中的for循环是为了表示一个进度的...的切换,如果换成一个gif图标更佳,foreach循环是完成list中各项任务的。如果运行,你会发现,窗体上是空白的,18行的给messageLabel.Text不起作用。
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
foreach (var item in list)
{
var dotString = "";
for (var i = 0; i < item.Time; i++)
{
if (i % 6 == 0)
{
dotString = ".";
}
else
{
dotString += ".";
}
messageLabel.Text = #34;{item.Name}{dotString}";
SpinWait.SpinUntil(() => false, 300);
}
}
MessageBox.Show("完成医保所有数据同步");
});
}
第二版:为了更新UI线程,在新线程中用this.Invoke来更新UI上控件的值,如18行。这回没有什么问题了,UI也能即时更新,看上去很完美。
private void button2_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
foreach (var item in list)
{
var dotString = "";
for (var i = 0; i < item.Time; i++)
{
if (i % 6 == 0)
{
dotString = ".";
}
else
{
dotString += ".";
}
this.Invoke(() =>
{
messageLabel.Text = #34;{item.Name}{dotString}";
});
SpinWait.SpinUntil(() => false, 300);
}
}
MessageBox.Show("完成医保所有数据同步");
});
}
其实这背后是有异常的(有可能会在vs中报出来),因为当你关闭窗体时,18行的this已经不存在了,但访问this.Invoke在新的线程中,新线程本身并没有关掉,这时就会报找不到实例,有可能你运行起来并不会发现异常,这是因为主线程关闭后,所有创建的子线程都会关闭的,为了验证,你可以5行前面加一个try,在catch中把异常内容写在一个日志文件中。
我的结果是:
Cannot access a disposed object.
Object name: 'Form1'.
第三版:发现问题,那就解决吧(有可能你觉得无所谓,窗体都关闭了,没报异常,看不出来。确实,如果你觉得无所谓,那就无所谓,这里只是来说明技术点的,不是来说服想法的),通过一个mark标志,在关闭窗体时,拦截一下,把子线程关闭,然后再把主窗体关闭,这样就没有问题了,自己起的线程,自己要关掉。
static bool mark = true;
private void startButton_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
foreach (var item in list)
{
if (mark == false)
{
break;
}
var dotString = "";
for (var i = 0; i < item.Time; i++)
{
if (mark == false)
{
break;
}
if (i % 6 == 0)
{
dotString = ".";
}
else
{
dotString += ".";
}
this.Invoke(() =>
{
messageLabel.Text = #34;{item.Name}{dotString}";
});
SpinWait.SpinUntil(() => false, 300);
}
}
if (mark)
{
mark = false;
MessageBox.Show("完成医保所有数据同步");
}
else
{
this.Invoke(() =>
{
this.Close();
});
}
});
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (mark)
{
e.Cancel = true;
mark = false;
}
}
线程是个大话题,这只是简单的线程协同,两个线程没有深度协同。通过这个例子,三个版本,一步一步来满足功能,提升健壮性和安全性,我们可以得出,做了一个功能,是需要下功夫的,不但要知其然,还要知其所以然。
- 上一篇: 一文教你轻松搞定ANR异常捕获与分析方法
- 下一篇: 多线程软件开发中怎么“优雅”的更新界面UI
猜你喜欢
- 2025-02-03 Qt源码分析之moveToThread(qt源代码)
- 2025-02-03 回不去的“魅族” 或用15刻画黄章新理念
- 2025-02-03 6 个会让 Flutter 应用崩溃的关键错误(以及如何避免它们)
- 2025-02-03 写给设计师的程序开发基本概念(写给设计师的程序开发基本概念是什么)
- 2025-02-03 Win7终于官宣退役:情怀无价 但请面向未来
- 2025-02-03 极空间私有云Q4体验:不止是存储工具,更是智能的数据管家
- 2025-02-03 32、64位版本!揭Ubuntu 14.10系统性能
- 2025-02-03 面试官:能说说HandlerThread的原理和使用场景吗?
- 2025-02-03 浅析RunLoop原理及其应用(runloop底层原理)
- 2025-02-03 Qt在多个子线程中更新UI(qt子线程向主线程发消息)
- 1507℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 519℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 489℃MySQL service启动脚本浅析(r12笔记第59天)
- 468℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 466℃启用MySQL查询缓存(mysql8.0查询缓存)
- 446℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 426℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 423℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)