做论坛网站价格,融水县住房和城乡建设局网站,广告素材网站,如何注册有限公司目录
1.1 简介1.2 在线程池中调用委托1.3 向线程池中放入异步操作1.4 线程池与并行度1.5 实现一个取消选项1.6 在线程池中使用等待事件处理器及超时1.7 使用计时器1.8 使用BackgroundWorker组件参考书籍笔者水平有限#xff0c;如果错误欢迎各位批评指正#xff01;1.1 简介…目录
1.1 简介1.2 在线程池中调用委托1.3 向线程池中放入异步操作1.4 线程池与并行度1.5 实现一个取消选项1.6 在线程池中使用等待事件处理器及超时1.7 使用计时器1.8 使用BackgroundWorker组件参考书籍笔者水平有限如果错误欢迎各位批评指正1.1 简介#
在本章中主要介绍线程池(ThreadPool)的使用在C#中它叫System.Threading.ThreadPool在使用线程池之前首先我们得明白一个问题那就是为什么要使用线程池。其主要原因是创建一个线程的代价是昂贵的创建一个线程会消耗很多的系统资源。
那么线程池是如何解决这个问题的呢线程池在初始时会自动创建一定量的线程供程序调用使用时开发人员并不直接分配线程而是将需要做的工作放入线程池工作队列中由线程池分配已有的线程进行处理等处理完毕后线程不是被销毁而是重新回到线程池中这样节省了创建线程的开销。
但是在使用线程池时需要注意以下几点这将非常重要。 线程池不适合处理长时间运行的作业或者处理需要与其它线程同步的作业。避免将线程池中的工作线程分配给I/O首先的任务这种任务应该使用TPL模型。如非必须不要手动设置线程池的最小线程数和最大线程数CLR会自动的进行线程池的扩张和收缩手动干预往往让性能更差。1.2 在线程池中调用委托#
本节展示的是如何在线程池中如何异步的执行委托然后将介绍一个叫异步编程模型(Asynchronous Programming Model简称APM)的异步编程方式。
在本节及以后为了降低代码量在引用程序集声明位置默认添加了using static System.Console和using static System.Threading.Thead声明这样声明可以让我们在程序中少些一些意义不大的调用语句。
演示代码如下所示使用了普通创建线程和APM方式来执行同一个任务。 Copy
static void Main(string[] args) { int threadId 0; RunOnThreadPool poolDelegate Test; var t new Thread(() Test(out threadId)); t.Start(); t.Join(); WriteLine($手动创建线程 Id: {threadId}); // 使用APM方式 进行异步调用 异步调用会使用线程池中的线程 IAsyncResult r poolDelegate.BeginInvoke(out threadId, Callback, 委托异步调用); r.AsyncWaitHandle.WaitOne(); // 获取异步调用结果 string result poolDelegate.EndInvoke(out threadId, r); WriteLine($Thread - 线程池工作线程Id: {threadId}); WriteLine(result); Console.ReadLine(); } // 创建带一个参数的委托类型 private delegate string RunOnThreadPool(out int threadId); private static void Callback(IAsyncResult ar) { WriteLine(Callback - 开始运行Callback...); WriteLine($Callback - 回调传递状态: {ar.AsyncState}); WriteLine($Callback - 是否为线程池线程: {CurrentThread.IsThreadPoolThread}); WriteLine($Callback - 线程池工作线程Id: {CurrentThread.ManagedThreadId}); } private static string Test(out int threadId) { string isThreadPoolThread CurrentThread.IsThreadPoolThread ? ThreadPool - : Thread - ; WriteLine(${isThreadPoolThread}开始运行...); WriteLine(${isThreadPoolThread}是否为线程池线程: {CurrentThread.IsThreadPoolThread}); Sleep(TimeSpan.FromSeconds(2)); threadId CurrentThread.ManagedThreadId; return ${isThreadPoolThread}线程池工作线程Id: {threadId}; }
运行结果如下图所示其中以Thread开头的为手动创建的线程输出的信息而TheadPool为开始线程池任务输出的信息Callback为APM模式运行任务结束后执行的回调方法可以清晰的看到Callback的线程也是线程池的工作线程。 在上文中使用BeginOperationName/EndOperationName方法和.Net中的IAsyncResult对象的方式被称为异步编程模型(或APM模式)这样的方法被称为异步方法。使用委托的BeginInvoke方法来运行该委托BeginInvoke接收一个回调函数该回调函数会在任务处理完成后背调用并且可以传递一个用户自定义的状态给回调函数。
现在这种APM编程方式用的越来越少了更推荐使用任务并行库(Task Parallel Library简称TPL)来组织异步API。
1.3 向线程池中放入异步操作#
本节将介绍如何将异步操作放入线程池中执行并且如何传递参数给线程池中的线程。本节中主要用到的是ThreadPool.QueueUserWorkItem()方法该方法可将需要运行的任务通过委托的形式传递给线程池中的线程并且允许传递参数。
使用比较简单演示代码如下所示。演示了线程池使用中如何传递方法和参数最后需要注意的是使用了Lambda表达式和它的闭包机制。 Copy
static void Main(string[] args) { const int x 1; const int y 2; const string lambdaState lambda state 2; // 直接将方法传递给线程池 ThreadPool.QueueUserWorkItem(AsyncOperation); Sleep(TimeSpan.FromSeconds(1)); // 直接将方法传递给线程池 并且 通过state传递参数 ThreadPool.QueueUserWorkItem(AsyncOperation, async state); Sleep(TimeSpan.FromSeconds(1)); // 使用Lambda表达式将任务传递给线程池 并且通过 state传递参数 ThreadPool.QueueUserWorkItem(state { WriteLine($Operation state: {state}); WriteLine($工作线程 id: {CurrentThread.ManagedThreadId}); Sleep(TimeSpan.FromSeconds(2)); }, lambda state); // 使用Lambda表达式将任务传递给线程池 通过 **闭包** 机制传递参数 ThreadPool.QueueUserWorkItem(_ { WriteLine($Operation state: {x y}, {lambdaState}); WriteLine($工作线程 id: {CurrentThread.ManagedThreadId}); Sleep(TimeSpan.FromSeconds(2)); }, lambda state); ReadLine(); } private static void AsyncOperation(object state) { WriteLine($Operation state: {state ?? (null)}); WriteLine($工作线程 id: {CurrentThread.ManagedThreadId}); Sleep(TimeSpan.FromSeconds(2)); }
运行结果如下图所示。 1.4 线程池与并行度#
在本节中主要是使用普通创建线程和使用线程池内的线程在任务量比较大的情况下有什么区别我们模拟了一个场景创建了很多不同的线程然后分别使用普通创建线程方式和线程池方式看看有什么不同。 Copy
static void Main(string[] args) { const int numberOfOperations 500; var sw new Stopwatch(); sw.Start(); UseThreads(numberOfOperations); sw.Stop(); WriteLine($使用线程执行总用时: {sw.ElapsedMilliseconds}); sw.Reset(); sw.Start(); UseThreadPool(numberOfOperations); sw.Stop(); WriteLine($使用线程池执行总用时: {sw.ElapsedMilliseconds}); Console.ReadLine(); } static void UseThreads(int numberOfOperations) { using (var countdown new CountdownEvent(numberOfOperations)) { WriteLine(通过创建线程调度工作); for (int i 0; i numberOfOperations; i) { var thread new Thread(() { Write(${CurrentThread.ManagedThreadId},); Sleep(TimeSpan.FromSeconds(0.1)); countdown.Signal(); }); thread.Start(); } countdown.Wait(); WriteLine(); } } static void UseThreadPool(int numberOfOperations) { using (var countdown new CountdownEvent(numberOfOperations)) { WriteLine(使用线程池开始工作); for (int i 0; i numberOfOperations; i) { ThreadPool.QueueUserWorkItem(_ { Write(${CurrentThread.ManagedThreadId},); Sleep(TimeSpan.FromSeconds(0.1)); countdown.Signal(); }); } countdown.Wait(); WriteLine(); } }
执行结果如下可见使用原始的创建线程执行速度非常快。只花了2秒钟但是创建了500多个线程而使用线程池相对来说比较慢花了9秒钟但是只创建了很少的线程为操作系统节省了线程和内存空间但花了更多的时间。 1.5 实现一个取消选项#
在之前的文章中有提到如果需要终止一个线程的执行那么可以使用Abort()方法但是有诸多的原因并不推荐使用Abort()方法。
这里推荐的方式是使用协作式取消(cooperative cancellation)这是一种可靠的技术来安全取消不再需要的任务。其主要用到CancellationTokenSource和CancellationToken两个类具体用法见下面演示代码。
以下延时代码主要是实现了使用CancellationToken和CancellationTokenSource来实现任务的取消。但是任务取消后可以进行三种操作分别是直接返回、抛出ThrowIfCancellationRequesed异常和执行回调。详细请看代码。 Copy
static void Main(string[] args) { // 使用CancellationToken来取消任务 取消任务直接返回 using (var cts new CancellationTokenSource()) { CancellationToken token cts.Token; ThreadPool.QueueUserWorkItem(_ AsyncOperation1(token)); Sleep(TimeSpan.FromSeconds(2)); cts.Cancel(); } // 取消任务 抛出 ThrowIfCancellationRequesed 异常 using (var cts new CancellationTokenSource()) { CancellationToken token cts.Token; ThreadPool.QueueUserWorkItem(_ AsyncOperation2(token)); Sleep(TimeSpan.FromSeconds(2)); cts.Cancel(); } // 取消任务 并 执行取消后的回调函数 using (var cts new CancellationTokenSource()) { CancellationToken token cts.Token; token.Register(() { WriteLine(第三个任务被取消执行回调函数。); }); ThreadPool.QueueUserWorkItem(_ AsyncOperation3(token)); Sleep(TimeSpan.FromSeconds(2)); cts.Cancel(); } ReadLine(); } static void AsyncOperation1(CancellationToken token) { WriteLine(启动第一个任务.); for (int i 0; i 5; i) { if (token.IsCancellationRequested) { WriteLine(第一个任务被取消.); return; } Sleep(TimeSpan.FromSeconds(1)); } WriteLine(第一个任务运行完成.); } static void AsyncOperation2(CancellationToken token) { try { WriteLine(启动第二个任务.); for (int i 0; i 5; i) { token.ThrowIfCancellationRequested(); Sleep(TimeSpan.FromSeconds(1)); } WriteLine(第二个任务运行完成.); } catch (OperationCanceledException) { WriteLine(第二个任务被取消.); } } static void AsyncOperation3(CancellationToken token) { WriteLine(启动第三个任务.); for (int i 0; i 5; i) { if (token.IsCancellationRequested) { WriteLine(第三个任务被取消.); return; } Sleep(TimeSpan.FromSeconds(1)); } WriteLine(第三个任务运行完成.); }
运行结果如下所示符合预期结果。 1.6 在线程池中使用等待事件处理器及超时#
本节将介绍如何在线程池中使用等待任务和如何进行超时处理其中主要用到ThreadPool.RegisterWaitForSingleObject()方法该方法允许传入一个WaitHandle对象和需要执行的任务、超时时间等。通过使用这个方法可完成线程池情况下对超时任务的处理。
演示代码如下所示运行了两次使用ThreadPool.RegisterWaitForSingleObject()编写超时代码的RunOperations()方法但是所传入的超时时间不同所以造成一个必然超时和一个不会超时的结果。 Copy
static void Main(string[] args) { // 设置超时时间为 5s WorkerOperation会延时 6s 肯定会超时 RunOperations(TimeSpan.FromSeconds(5)); // 设置超时时间为 7s 不会超时 RunOperations(TimeSpan.FromSeconds(7)); } static void RunOperations(TimeSpan workerOperationTimeout) { using (var evt new ManualResetEvent(false)) using (var cts new CancellationTokenSource()) { WriteLine(注册超时操作...); // 传入同步事件 超时处理函数 和 超时时间 var worker ThreadPool.RegisterWaitForSingleObject(evt , (state, isTimedOut) WorkerOperationWait(cts, isTimedOut) , null , workerOperationTimeout , true); WriteLine(启动长时间运行操作...); ThreadPool.QueueUserWorkItem(_ WorkerOperation(cts.Token, evt)); Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds(2))); // 取消注册等待的操作 worker.Unregister(evt); ReadLine(); } } static void WorkerOperation(CancellationToken token, ManualResetEvent evt) { for (int i 0; i 6; i) { if (token.IsCancellationRequested) { return; } Sleep(TimeSpan.FromSeconds(1)); } evt.Set(); } static void WorkerOperationWait(CancellationTokenSource cts, bool isTimedOut) { if (isTimedOut) { cts.Cancel(); WriteLine(工作操作超时并被取消.); } else { WriteLine(工作操作成功.); } }
运行结果如下图所示与预期结果相符。 1.7 使用计时器#
计时器是FCL提供的一个类叫System.Threading.Timer可要结果与创建周期性的异步操作。该类使用比较简单。
以下的演示代码使用了定时器并设置了定时器延时启动时间和周期时间。 Copy
static void Main(string[] args) { WriteLine(按下回车键结束定时器...); DateTime start DateTime.Now; // 创建定时器 _timer new Timer(_ TimerOperation(start), null , TimeSpan.FromSeconds(1) , TimeSpan.FromSeconds(2)); try { Sleep(TimeSpan.FromSeconds(6)); _timer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(4)); ReadLine(); } finally { //实现了IDispose接口 要及时释放 _timer.Dispose(); } } static Timer _timer; static void TimerOperation(DateTime start) { TimeSpan elapsed DateTime.Now - start; WriteLine($离 {start} 过去了 {elapsed.Seconds} 秒. $定时器线程池 线程 id: {CurrentThread.ManagedThreadId}); }
运行结果如下所示可见定时器根据所设置的周期时间循环的调用TimerOperation()方法。 1.8 使用BackgroundWorker组件#
本节主要介绍BackgroundWorker组件的使用该组件实际上被用于Windows窗体应用程序(Windows Forms Application简称 WPF)中通过它实现的代码可以直接与UI控制器交互更加自认和好用。
演示代码如下所示使用BackgroundWorker来实现对数据进行计算并且让其支持报告工作进度支持取消任务。 Copy
static void Main(string[] args) { var bw new BackgroundWorker(); // 设置可报告进度更新 bw.WorkerReportsProgress true; // 设置支持取消操作 bw.WorkerSupportsCancellation true; // 需要做的工作 bw.DoWork Worker_DoWork; // 工作处理进度 bw.ProgressChanged Worker_ProgressChanged; // 工作完成后处理函数 bw.RunWorkerCompleted Worker_Completed; bw.RunWorkerAsync(); WriteLine(按下 C 键 取消工作); do { if (ReadKey(true).KeyChar C) { bw.CancelAsync(); } } while (bw.IsBusy); } static void Worker_DoWork(object sender, DoWorkEventArgs e) { WriteLine($DoWork 线程池 线程 id: {CurrentThread.ManagedThreadId}); var bw (BackgroundWorker)sender; for (int i 1; i 100; i) { if (bw.CancellationPending) { e.Cancel true; return; } if (i % 10 0) { bw.ReportProgress(i); } Sleep(TimeSpan.FromSeconds(0.1)); } e.Result 42; } static void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { WriteLine($已完成{e.ProgressPercentage}%. $处理线程 id: {CurrentThread.ManagedThreadId}); } static void Worker_Completed(object sender, RunWorkerCompletedEventArgs e) { WriteLine($完成线程池线程 id: {CurrentThread.ManagedThreadId}); if (e.Error ! null) { WriteLine($异常 {e.Error.Message} 发生.); } else if (e.Cancelled) { WriteLine($操作已被取消.); } else { WriteLine($答案是 : {e.Result}); } }
运行结果如下所示。 在本节中使用了C#中的另外一个语法叫事件(event)。当然这里的事件不同于之前在线程同步章节中提到的事件这里是观察者设计模式的体现包括事件源、订阅者和事件处理程序。因此除了异步APM模式意外还有基于事件的异步模式(Event-based Asynchronous Pattern简称 EAP)。
参考书籍
本文主要参考了以下几本书在此对这些作者表示由衷的感谢你们提供了这么好的资料。 《CLR via C#》《C# in Depth Third Edition》《Essential C# 6.0》《Multithreading with C# Cookbook Second Edition》《C#多线程编程实战》源码下载点击链接 示例源码下载
笔者水平有限如果错误欢迎各位批评指正
作者InCerry
出处https://www.cnblogs.com/InCerry/p/9432804.html
版权本文采用「署名 4.0 国际」知识共享许可协议进行许可。