浅谈.NET下的多线程和并行计算(七)基于多线程的基本组件

简介: 在多线程应用中我们有一些很常见的需求,比如定时去做计划任务,或者是在执行一个长时间的任务,在执行这个任务的过程中能有进度显示(能想到要实现这个需求需要新开一个线程,避免阻塞UI的更新)。对于这些应用.NET提供了现成的组件。

在多线程应用中我们有一些很常见的需求,比如定时去做计划任务,或者是在执行一个长时间的任务,在执行这个任务的过程中能有进度显示(能想到要实现这个需求需要新开一个线程,避免阻塞UI的更新)。对于这些应用.NET提供了现成的组件。

首先来看一下System.Threading的Timer组件,它提供了定时执行某个任务的方法:

ThreadPool.SetMinThreads(2, 2);
ThreadPool.SetMaxThreads(4, 4);

Timer timer = new Timer((state) =>
{
    int a, b;
    ThreadPool.GetAvailableThreads(out a, out b);
    Console.WriteLine(string.Format("({0}/{1}) #{2} : {3}", a, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("mm:ss")));
}, null, 2000, 1000);

Console.WriteLine(DateTime.Now.ToString("mm:ss"));
Thread.Sleep(5000);
Console.WriteLine("Change()");
timer.Change(3000, 500);
Thread.Sleep(5000);
Console.WriteLine("Dispose()");
timer.Dispose();
Thread.Sleep(5000);
Console.WriteLine(DateTime.Now.ToString("mm:ss"));

这段代码的运行结果如下:

image

我们可以看到:

1) Timer构造方法中第一个参数就是要定时执行的方法,这个方法接受一个状态参数,第二个参数是状态参数,第三个参数是首次调用回调方法前的延迟毫秒,第四个参数就是执行方法的间隔毫秒

2) 从结果中我们可以看到,第一次回调方法在2秒后执行,然后每一秒执行一次,之后我们调用了Change()方法把延迟时间设置为3秒,把间隔设置为500毫秒,看到Timer在完成了上次回调之后3秒后执行了新的回调,之后间隔500毫秒执行一次。

3) 最后,我们执行了Dispose()方法,在结束最后一次回调之后Timer就再也没有调用回调方法。

4) 在回调方法中我们输出了线程池的可用线程,可以看到Timer基于线程池,也就是Timer基于后台线程。

.NET中还提供了System.Timers.Timer,它封装并增强了System.Threading.Timer:

System.Timers.Timer timer2 = new System.Timers.Timer();
timer2.Elapsed += new System.Timers.ElapsedEventHandler(timer2_Elapsed);
timer2.Interval = 1000;
Console.WriteLine("Start()");
timer2.Start();
Console.WriteLine(DateTime.Now.ToString("mm:ss"));
Thread.Sleep(5000);
Console.WriteLine("Stop()");
timer2.Stop();
Thread.Sleep(5000);
Console.WriteLine("Change Interval and Start()");
timer2.Interval = 500;
timer2.Start();
Thread.Sleep(5000);
Console.WriteLine("Dispose()");
timer2.Dispose();
Thread.Sleep(5000);
Console.WriteLine(DateTime.Now.ToString("mm:ss"));
 
static void timer2_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    int a, b;
    ThreadPool.GetAvailableThreads(out a, out b);
    Console.WriteLine(string.Format("({0}/{1}) #{2} : {3}", a, b, Thread.CurrentThread.ManagedThreadId, e.SignalTime.ToString("mm:ss")));
}

(假设我们还是设置线程池最小2个线程最大4个线程)

这段代码的结果如下:

image

从运行结果中我们可以看到:

1) 由于System.Timers.Timer封装了System.Threading.Timer,所以还是基于线程池

2) 默认Timer是停止的,启动后需要等待一个Interval再执行回调方法

最后,再来看看BackgroundWorker,它提供了对于前面说的执行一个任务,在UI上更新进度这种应用的封装,首先定义一个static的BackgroundWorker:

static BackgroundWorker bw = new BackgroundWorker();

然后写如下测试代码:

bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.Disposed += new EventHandler(bw_Disposed);
AutoResetEvent are = new AutoResetEvent(false);
Console.WriteLine(DateTime.Now.ToString("mm:ss"));
bw.RunWorkerAsync(are);
Thread.Sleep(2000);
are.Set();
Thread.Sleep(2000);
Console.WriteLine("CancelAsync()");
bw.CancelAsync();
while (bw.IsBusy)
    Thread.Sleep(10);
bw.Dispose();

这段代码中我们:

1) 设置BackgroundWorker可以汇报进度(通过ProgressChanged事件)

2) 设置BackgroundWorker支持任务取消

3) 定义了进度更新的处理事件:

static void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    Console.WriteLine(string.Format("{0} : {1}% completed", DateTime.Now.ToString("mm:ss"), e.ProgressPercentage));
}

4) 定义了任务完成的处理事件:

static void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
        Console.WriteLine("Cancelled");
    else if (e.Error != null)
        Console.WriteLine(e.Error.ToString());
    else
        Console.WriteLine(e.Result);
    Console.WriteLine(DateTime.Now.ToString("mm:ss"));
}

5) 定义了任务的主体方法:

static void bw_DoWork(object sender, DoWorkEventArgs e)
{
    (e.Argument as AutoResetEvent).WaitOne();

    for (int i = 0; i <= 100; i += 10)
    {
        //if (bw.CancellationPending)
        //{
        //    Console.WriteLine("Cancelling...");
        //    for (int j = 0; j <= 100; j += 20)
        //    {
        //        bw.ReportProgress(j);
        //        Thread.Sleep(500);
        //    }
        //    e.Cancel = true;
        //    return;
        //}
        bw.ReportProgress(i);
        Thread.Sleep(500);
    }
    e.Result = 100;
}

6) 定义了Dispose BackgroundWorker后的事件:

static void bw_Disposed(object sender, EventArgs e)
{
    Console.WriteLine("disposed");
}

7) 使用信号量作为事件的状态参数让任务延迟2秒执行

8) 主线程通过IsBusy判断任务是否在执行,轮询等待

9) 最后Dispose这个组件

程序执行结果如下:

image

可以看到:

1) 任务延迟2秒执行,任务分为10个阶段执行,每执行一个阶段汇报一下进度。每个阶段需要500毫秒

2) 任务执行完成之后可以设置Result属性的值,这个值在bw_RunWorkerCompleted中可以获取到

我们还原注释的那些代码来看看取消任务的情况:

image

我们看到任务在2秒后取消,要注意这种异步取消任务的方式,我们调用了CancelAsync()其实不能从实质上取消任务的执行,要真正取消任务需要在任务的主体方法中不断检测CancellationPending属性,如果为true表示用户希望取消任务,然后去执行一些取消任务的行为,在完成后设置Cancel属性为true,并结束任务主体,这么做的目的是因为对于长时间的任务取消回滚的过程可能也是长时间的,我们同样可以在主体方法中对取消的行为进行进度汇报。您可以自己做相关实验,可以发现在控制台程序中,BackgroundWorker的DoWork可ProgressReport基于两个独立的线程,这两个线程都是基于线程池的,在Winform中 BackgroundWorker的DoWork基于线程池中的独立线程而ProgressReport执行于UI线程。

作者: lovecindywang
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
13天前
|
Java API 调度
【JavaEE】——多线程(join阻塞,计算,引用,状态)
【JavaEE】——多线程,join,sleep引起的线程阻塞,多线程提升计算效率,如何获取线程的引用和状态
|
2月前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
3月前
|
并行计算 安全 Java
Python 多线程并行执行详解
Python 多线程并行执行详解
92 3
|
5月前
|
算法 Java
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
该博客文章综合介绍了Java并发编程的基础知识,包括线程与进程的区别、并发与并行的概念、线程的生命周期状态、`sleep`与`wait`方法的差异、`Lock`接口及其实现类与`synchronized`关键字的对比,以及生产者和消费者问题的解决方案和使用`Condition`对象替代`synchronized`关键字的方法。
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
|
5月前
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
120 1
|
4月前
|
SQL 存储 监控
SQLServer事务复制延迟优化之并行(多线程)复制
【9月更文挑战第12天】在SQL Server中,事务复制延迟会影响数据同步性。并行复制可通过多线程处理优化这一问题,提高复制效率。主要优化方法包括:配置分发代理参数、优化网络带宽、调整系统资源、优化数据库设计及定期监控维护。合理实施这些措施可提升数据同步的及时性和可靠性。
111 0
|
5月前
|
算法 Java
JDK版本特性问题之想控制 G1 垃圾回收器的并行工作线程数量,如何解决
JDK版本特性问题之想控制 G1 垃圾回收器的并行工作线程数量,如何解决
|
6月前
|
SQL 安全
线程操纵术并行策略问题之调整并行流的并行度问题如何解决
线程操纵术并行策略问题之调整并行流的并行度问题如何解决
|
15天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
44 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
65 1