浅谈.NET下的多线程和并行计算(九)Winform中多线程编程基础下

简介: 在之前的文章中我们介绍过两种Timer和BackgroundWorker组件,在上文中我们提到过,强烈建议在UI线程上操作控件,否则很容易产生人品问题。可以想到,上次介绍的两个Timer基于ThreadPool,回调方法运行于不同于UI线程的新线程上,在这个方法中操作控件需要进行Invoke或BeginInvoke。

在之前的文章中我们介绍过两种Timer和BackgroundWorker组件,在上文中我们提到过,强烈建议在UI线程上操作控件,否则很容易产生人品问题。可以想到,上次介绍的两个Timer基于ThreadPool,回调方法运行于不同于UI线程的新线程上,在这个方法中操作控件需要进行Invoke或BeginInvoke。其实,还有第三种System.Windows.Forms.Timer,它可以让回调事件在UI线程上执行,我们来做一个实验比较一下System.Windows.Forms.Timer和System.Timers.Timer。在一个窗体上新建两个标签控件,然后来创建两个计时器:

private System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
private System.Timers.Timer timer2 = new System.Timers.Timer();

public Form2()
{
    InitializeComponent();
}

双击表单,在Load事件中写如下代码:

this.Text = string.Concat("#", Thread.CurrentThread.ManagedThreadId);
//timer2.SynchronizingObject = this;
timer2.Interval = 1000;
timer2.Elapsed += new System.Timers.ElapsedEventHandler(timer2_Elapsed);
timer2.Enabled = true;
timer1.Interval = 1000;
timer1.Tick += new System.EventHandler(this.timer1_Tick);
timer1.Enabled = true;

然后是两个计时器的处理方法:

private void timer1_Tick(object sender, EventArgs e)
{
    label1.Text = string.Format("timer1 : #{0} {1} {2}", Thread.CurrentThread.ManagedThreadId, this.InvokeRequired, DateTime.Now.ToString());
}

private void timer2_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    label2.Text = string.Format("timer2 : #{0} {1} {2}", Thread.CurrentThread.ManagedThreadId, this.InvokeRequired, DateTime.Now.ToString());
}

以非调试方式运行程序,可以看到如下的结果:

image

分析一下这个结果

1) UI托管线程Id是1

2) System.Windows.Forms.Timer运行于UI线程,不需要Invoke

3) System.Timers.Timer运行于新的线程,需要Invoke

也就是说如果以调试方式运行程序的话,label2控件的赋值操作将会失败。要解决这个问题有两个办法:

1) 取消注释 timer2.SynchronizingObject = this; 一句使回调方法运行于UI线程(等同于System.Windows.Forms.Timer的效果)

2) 在回调方法内涉及到UI的操作使用Invoke或BeginInvoke

您可以实验一下,在回调方法中阻塞线程的话,UI被阻塞,界面停止响应,因此,如果您的回调方法需要比较耗时操作的话建议使用灵活性更大的System.Timers.Timer,手动使用Invoke或BeginInvoke,当然如果回调方法主要用于操作UI可以直接使用System.Windows.Forms.Timer。

对于BackgroundWorker组件我们可以想到通常会希望后台操作运行于工作线程上,而进度汇报的方法运行于UI线程上,其实它就是这么做的,我们可以直接在工具箱中拖一个BackgroundWorker到表单设计器上,然后在表单上添加如下控件:

image

分别是一个ProgressBar,两个按钮和两个标签。对于BackgroundWorker,我们直接通过属性窗口打开它的进度汇报和取消功能:

image

然后添加三个事件的处理方法:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    for (int i = 0; i <= 100; i += 10)
    {
        if (backgroundWorker1.CancellationPending)
        {
            for (int j = i; j >=0; j -= 10)
            {
                backgroundWorker1.ReportProgress(j);
                Thread.Sleep(500);
            }
            e.Cancel = true;
            return;
        }
        label1.Text = string.Format("worker : #{0} {1} {2}", Thread.CurrentThread.ManagedThreadId, this.InvokeRequired, DateTime.Now.ToString());
        backgroundWorker1.ReportProgress(i);
        Thread.Sleep(500);
    }
    e.Result = 100;
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    label2.Text = string.Format("progress : #{0} {1} {2}", Thread.CurrentThread.ManagedThreadId, this.InvokeRequired, DateTime.Now.ToString());
    progressBar1.Value = e.ProgressPercentage;
}

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
        MessageBox.Show("Cancelled");
    else if (e.Error != null)
        MessageBox.Show(e.Error.ToString());
    else
        MessageBox.Show(e.Result.ToString());
}

两个按钮分别用于开始后体工作和取消后台工作:

private void button1_Click(object sender, EventArgs e)
{
    if (!backgroundWorker1.IsBusy)
        backgroundWorker1.RunWorkerAsync();
}

private void button2_Click(object sender, EventArgs e)
{
    if (backgroundWorker1.IsBusy)
        backgroundWorker1.CancelAsync();
}

在DoWork事件处理方法和ProgressChanged事件处理方法中我们在相应的标签上输出托管线程Id。在DoWork事件处理方法中,我们也实现了取消任务后的回滚操作,以非调试方式运行程序并且点击开始按钮后的效果如下:

image

可以看到,ProgressChanged事件处理方法运行于UI线程,而DoWork事件处理方法运行于新线程上,这符合我们的期望。

本质上,BackgroundWorker的进度汇报基于AsyncOperation,差不多如下:

AsyncOperation asyncOperation;
public Form4()
{
    InitializeComponent();
    this.Text = string.Concat("#", Thread.CurrentThread.ManagedThreadId);
    asyncOperation = AsyncOperationManager.CreateOperation(null);
}

private void button1_Click(object sender, EventArgs e)
{
    new Thread(() =>
    {
        for (int i = 0; i <= 100; i += 10)
        {
            label1.Text = string.Format("worker : #{0} {1} {2}", Thread.CurrentThread.ManagedThreadId, this.InvokeRequired, DateTime.Now.ToString());
            asyncOperation.Post(new SendOrPostCallback((j)=>
            {
                label2.Text = string.Format("progress : #{0} {1} {2}", Thread.CurrentThread.ManagedThreadId, this.InvokeRequired, DateTime.Now.ToString());
                progressBar1.Value = (int)j;
            }), i);
            Thread.Sleep(500);
        }
    }) { IsBackground = true }.Start();
}

而AsyncOperation又是基于SynchronizationContext的封装。个人认为AsyncOperation和SynchronizationContext达到的效果和Invoke以及BeginInvoke差不多,而且MSDN上也并没有很多介绍AsyncOperation和SynchronizationContext的篇幅,因此,在不使用BackgroundWorker的时候我还是更愿意去直接使用Invoke和BeinInvoke这种原始的方法。

后面几节我们将介绍.NET异步编程模型APM。

作者: lovecindywang
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
11月前
|
网络协议 C#
基于.NET WinForm开发的一款硬件及协议通讯工具
基于.NET WinForm开发的一款硬件及协议通讯工具
132 7
|
前端开发 Android开发
WinForm 直接运行 Admin.NET
本文介绍了如何将 Admin.NET 以 WinForm 桌面程序模式运行,简化了手动配置 Web 服务的过程,便于演示和作为单机软件使用。通过添加特定 NuGet 包、修改 `Program.cs` 和 `Form1.cs` 文件,并调整项目配置,最终实现了在 WinForm 中嵌入 WebView 组件显示 Admin.NET 界面的效果。
161 0
WinForm 直接运行 Admin.NET
|
开发框架 Java .NET
.net core 非阻塞的异步编程 及 线程调度过程
【11月更文挑战第12天】本文介绍了.NET Core中的非阻塞异步编程,包括其基本概念、实现方式及应用示例。通过`async`和`await`关键字,程序可在等待I/O操作时保持线程不被阻塞,提高性能。文章还详细说明了异步方法的基础示例、线程调度过程、延续任务机制、同步上下文的作用以及如何使用`Task.WhenAll`和`Task.WhenAny`处理多个异步任务的并发执行。
257 1
|
11月前
|
Java API 调度
【JavaEE】——多线程(join阻塞,计算,引用,状态)
【JavaEE】——多线程,join,sleep引起的线程阻塞,多线程提升计算效率,如何获取线程的引用和状态
winform .net6 和 framework 的图表控件,为啥项目中不存在chart控件,该如何解决?
本文讨论了在基于.NET 6和.NET Framework的WinForms项目中添加图表控件的不同方法。由于.NET 6的WinForms项目默认不包含Chart控件,可以通过NuGet包管理器安装如ScottPlot等图表插件。而对于基于.NET Framework的WinForms项目,Chart控件是默认存在的,也可以通过NuGet安装额外的图表插件,例如LiveCharts。文中提供了通过NuGet添加图表控件的步骤和截图说明。
winform .net6 和 framework 的图表控件,为啥项目中不存在chart控件,该如何解决?
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
419 1
|
并行计算 Java 大数据
Java中的高效并行计算与多线程编程技术
Java中的高效并行计算与多线程编程技术
|
开发框架 监控 Java
【.NET Core】多线程之线程池(ThreadPool)详解(二)
【.NET Core】多线程之线程池(ThreadPool)详解(二)
323 3
|
SQL 开发框架 Java
【.NET Core】多线程之线程池(ThreadPool)详解(一)
【.NET Core】多线程之线程池(ThreadPool)详解(一)
785 2
|
设计模式 并行计算 安全
Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
214 0

热门文章

最新文章

下一篇
oss云网关配置