多线程之线程池篇3,多线程编程学习笔记
分类:计算机编程

目录

C#多线程之线程池篇3,

  在上一篇C#四线程之线程池篇2中,大家最首要学习了线程池和并行度以致哪些贯彻裁撤选项的有关文化。在这里一篇中,大家首要学习怎么样选择等待句柄和过期、使用停车计时器和行使BackgroundWorker组件的相关文化。

五、使用等待句柄和过期

  在这里一小节中,大家将学习怎么着在线程池中完结超时和不利地贯彻等待。具体操作步骤如下:

1、使用Visual Studio 2016创办二个新的调整台应用程序。

2、双击张开“Program.cs”文件,编写代码如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe05
 7 {
 8     class Program
 9     {
10         // CancellationTokenSource:通知System.Threading.CancellationToken,告知其应被取消。
11         static void WorkerOperationWait(CancellationTokenSource cts, bool isTimedOut)
12         {
13             if (isTimedOut)
14             {
15                 // 传达取消请求。
16                 cts.Cancel();
17                 WriteLine("Worker operation timed out and was canceled.");
18             }
19             else
20             {
21                 WriteLine("Worker operation succeeded.");
22             }
23         }
24 
25         // CancellationToken:传播有关应取消操作的通知。
26         // ManualResetEvent:通知一个或多个正在等待的线程已发生事件。
27         static void WorkerOperation(CancellationToken token, ManualResetEvent evt)
28         {
29             for (int i = 0; i < 6; i  )
30             {
31                 // 获取是否已请求取消此标记。如果已请求取消此标记,则为 true;否则为 false。
32                 if (token.IsCancellationRequested)
33                 {
34                     return;
35                 }
36                 Sleep(TimeSpan.FromSeconds(1));
37             }
38             // 将事件状态设置为终止状态,允许一个或多个等待线程继续。
39             evt.Set();
40         }
41 
42         static void RunOperations(TimeSpan workerOperationTimeout)
43         {
44             using (var evt = new ManualResetEvent(false))
45             using (var cts = new CancellationTokenSource())
46             {
47                 // 注册一个等待System.Threading.WaitHandle的委托,并指定一个System.TimeSpan值来表示超时时间。
48                 // 第一个参数:要注册的System.Threading.WaitHandle。使用System.Threading.WaitHandle而非 System.Threading.Mutex。
49                 // 第二个参数:waitObject参数终止时调用的System.Threading.WaitOrTimerCallback 委托。
50                 // 第三个参数:传递给委托的对象。
51                 // 第四个参数:System.TimeSpan表示的超时时间。如果timeout为0(零),则函数将测试对象的状态并立即返回。如果timeout为 -1,则函数的超时间隔永远不过期。
52                 // 第五个参数:如果为true,表示在调用了委托后,线程将不再在waitObject参数上等待;如果为false,表示每次完成等待操作后都重置计时器,直到注销等待。
53                 // 返回值:封装本机句柄的System.Threading.RegisteredWaitHandle。
54                 var worker = ThreadPool.RegisterWaitForSingleObject(evt, (state, isTimedOut) => WorkerOperationWait(cts, isTimedOut), null, workerOperationTimeout, true);
55 
56                 WriteLine("Starting long running operation...");
57                 // ThreadPool.QueueUserWorkItem:将方法排入队列以便执行。此方法在有线程池线程变得可用时执行。
58                 // cts.Token:获取与此System.Threading.CancellationTokenSource关联的System.Threading.CancellationToken。
59                 ThreadPool.QueueUserWorkItem(_ => WorkerOperation(cts.Token, evt));
60 
61                 Sleep(workerOperationTimeout.Add(TimeSpan.FromSeconds(2)));
62 
63                 // 取消由System.Threading.ThreadPool.RegisterWaitForSingleObject方法发出的已注册等待操作。
64                 worker.Unregister(evt);
65             }
66         }
67 
68         static void Main(string[] args)
69         {
70             // 实现超时
71             RunOperations(TimeSpan.FromSeconds(5));
72             // 实现等待
73             RunOperations(TimeSpan.FromSeconds(7));
74         }
75     }
76 }

3、运营该调整台应用程序,运维效果如下图所示:

图片 1

  线程池还应该有另一个有效的秘诀:ThreadPool.RegisterWaitForSingleObject,该办法允许大家将回调方法归入线程池的连串中,当所提供的等候句柄发送能量信号或然逾期爆发时,该回调方法即被推行。那允许大家对线程池中的操作达成超时。

  在第71行代码处,大家在主线程中调用了“RunOperations”方法,并给它的workerOperationTimeout参数字传送递了数值5,表示超时时间为5秒。

  在第54行代码处,我们调用了ThreadPool的“RegisterWaitForSingleObject”静态方法,并钦赐了回调方法所要实行的操作是“WorkerOperationWait”方法,超时时间是5秒。

  在第59行代码处,大家调用ThreadPool的“QueueUserWorkItem”静态方法来举行“WorkerOperation”方法,而该形式所消耗的年Nokia6秒,在这里六秒中内早就在线程池中发送了晚点,所以会执行第13~18行和第32~35行处的代码。

  在第73行代码处,我们传递了数值7给“RunOperations”方法,设置线程池的逾期时间为7秒,因为“WorkerOperation”方法的试行时间为6秒,所以在这里种景色下并没有生出超时,成功施行达成“WorkerOperation”方法。

 六、使用电磁打点计时器

  在此一小节中,大家将学习怎么着使用System.Threading.Timer对象在线程池中定期地调用贰个异步操作。具体操作步骤如下所示:

1、使用Visual Studio 2016成立三个新的调整台应用程序。

2、双击张开“Program.cs”文件,编写代码如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe06
 7 {
 8     class Program
 9     {
10         static Timer timer;
11 
12         static void TimerOperation(DateTime start)
13         {
14             TimeSpan elapsed = DateTime.Now - start;
15             WriteLine($"{elapsed.Seconds} seconds from {start}. Timer thread pool thread id: {CurrentThread.ManagedThreadId}");
16         }
17 
18         static void Main(string[] args)
19         {
20             WriteLine("Press 'Enter' to stop the timer...");
21             DateTime start = DateTime.Now;
22             // 初始化Timer类的新实例,使用System.TimeSpan值来度量时间间隔。
23             // 第一个参数:一个System.Threading.TimerCallback委托,表示要执行的方法。
24             // 第二个参数:一个包含回调方法要使用的信息的对象,或者为null。
25             // 第三个参数:System.TimeSpan,表示在callback参数调用它的方法之前延迟的时间量。指定-1毫秒以防止启动计时器。指定零(0)可立即启动计时器。
26             // 第四个参数:在调用callback所引用的方法之间的时间间隔。指定-1毫秒可以禁用定期终止。
27             timer = new Timer(_ => TimerOperation(start), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2));
28             try
29             {
30                 Sleep(TimeSpan.FromSeconds(6));
31                 // 更改计时器的启动时间和方法调用之间的时间间隔,使用System.TimeSpan值度量时间间隔。
32                 // 第一个参数:一个System.TimeSpan,表示在调用构造System.Threading.Timer时指定的回调方法之前的延迟时间量。指定负-1毫秒以防止计时器重新启动。指定零(0)可立即重新启动计时器。
33                 // 第二个参数:在构造System.Threading.Timer时指定的回调方法调用之间的时间间隔。指定-1毫秒可以禁用定期终止。
34                 timer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(4));
35                 ReadLine();
36             }
37             finally
38             {
39                 timer.Dispose();
40             }
41         }
42     }
43 }

3、运营该调控台应用程序,运维效果(每趟运转效果兴许分裂)如下图所示:

图片 2

  首先,大家成立了五个Timer实例,它的构造方法的首先个参数是贰个lambda表达式,表示要在线程池中实践的代码,在该表达式中我们调用了“TimerOperation”方法,并给它提供了二个起来时间值。由于大家未有动用state对象,由此大家给Timer的构造方法的第四个参数字传送递了null。第多个参数表示第壹遍施行“TimerOperation”所要开销的流年为1分钟。第五个参数表示每回调用“TimerOperation”之间的岁月间隔为2分钟。

  在主线程阻塞6分钟之后,大家调用了Timer实例的“Change”方法,更动了历次调用“TimerOperation”之间的年华间距为4分钟。

  最终,我们静观其变输入“Enter”键来收场应用程序。

七、使用BackgroundWorker组件

   在这里一小节中,我们学习其他一种异步编制程序的主意:BackgroundWorker组件。在此个组件的支援下,我们可以通过一层层事件和事件管理方法组织我们的异步代码。具体操作步骤如下所示:

1、使用Visual Studio 二〇一五创办一个新的调控台应用程序。

2、双击展开“Program.cs”文件,编写代码如下所示:

  1 using System;
  2 using System.ComponentModel;
  3 using System.Threading;
  4 using static System.Console;
  5 using static System.Threading.Thread;
  6 
  7 namespace Recipe07
  8 {
  9     class Program
 10     {
 11         static void WorkerDoWork(object sender, DoWorkEventArgs e)
 12         {
 13             WriteLine($"DoWork thread pool thread id: {CurrentThread.ManagedThreadId}");
 14             var bw = (BackgroundWorker)sender;
 15             for (int i = 1; i <= 100; i  )
 16             {
 17                 // 获取一个值,指示应用程序是否已请求取消后台操作。
 18                 // 如果应用程序已请求取消后台操作,则为 true;否则为 false。默认值为 false。
 19                 if (bw.CancellationPending)
 20                 {
 21                     e.Cancel = true;
 22                     return;
 23                 }
 24 
 25                 if (i % 10 == 0)
 26                 {
 27                     // 引发 System.ComponentModel.BackgroundWorker.ProgressChanged 事件。
 28                     // 参数:已完成的后台操作所占的百分比,范围从 0% 到 100%。
 29                     bw.ReportProgress(i);
 30                 }
 31 
 32                 Sleep(TimeSpan.FromSeconds(0.1));
 33             }
 34 
 35             // 获取或设置表示异步操作结果的值。
 36             e.Result = 42;
 37         }
 38 
 39         static void WorkerProgressChanged(object sender, ProgressChangedEventArgs e)
 40         {
 41             // e.ProgressPercentage:获取异步任务的进度百分比。
 42             WriteLine($"{e.ProgressPercentage}% completed. Progress thread pool thread id: {CurrentThread.ManagedThreadId}");
 43         }
 44 
 45         static void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 46         {
 47             WriteLine($"Completed thread pool thread id: {CurrentThread.ManagedThreadId}");
 48             // e.Error:获取一个值,该值指示异步操作期间发生的错误。
 49             if (e.Error != null)
 50             {
 51                 // 打印出异步操作期间发生的错误信息。
 52                 WriteLine($"Exception {e.Error.Message} has occured.");
 53             }
 54             else if (e.Cancelled) // 获取一个值,该值指示异步操作是否已被取消。
 55             {
 56                 WriteLine($"Operation has been canceled.");
 57             }
 58             else
 59             {
 60                 // e.Result:获取表示异步操作结果的值。
 61                 WriteLine($"The answer is: {e.Result}");
 62             }
 63         }
 64 
 65         static void Main(string[] args)
 66         {
 67             // 初始化System.ComponentModel.BackgroundWorker类的新实例。该类在单独的线程上执行操作。
 68             var bw = new BackgroundWorker();
 69             // 获取或设置一个值,该值指示System.ComponentModel.BackgroundWorker能否报告进度更新。
 70             // 如果System.ComponentModel.BackgroundWorker支持进度更新,则为true;否则为false。默认值为false。
 71             bw.WorkerReportsProgress = true;
 72             // 获取或设置一个值,该值指示System.ComponentModel.BackgroundWorker是否支持异步取消。
 73             // 如果System.ComponentModel.BackgroundWorker支持取消,则为true;否则为false。默认值为false。
 74             bw.WorkerSupportsCancellation = true;
 75 
 76             // 调用System.ComponentModel.BackgroundWorker.RunWorkerAsync时发生。
 77             bw.DoWork  = WorkerDoWork;
 78             // 调用System.ComponentModel.BackgroundWorker.ReportProgress(System.Int32)时发生。
 79             bw.ProgressChanged  = WorkerProgressChanged;
 80             // 当后台操作已完成、被取消或引发异常时发生。
 81             bw.RunWorkerCompleted  = WorkerCompleted;
 82 
 83             // 开始执行后台操作。
 84             bw.RunWorkerAsync();
 85 
 86             WriteLine("Press C to cancel work");
 87 
 88             do
 89             {
 90                 // 获取用户按下的下一个字符或功能键。按下的键可以选择显示在控制台窗口中。
 91                 // 确定是否在控制台窗口中显示按下的键。如果为 true,则不显示按下的键;否则为 false。
 92                 if (ReadKey(true).KeyChar == 'C')
 93                 {
 94                     // 请求取消挂起的后台操作。
 95                     bw.CancelAsync();
 96                 }
 97             }
 98             // 获取一个值,指示System.ComponentModel.BackgroundWorker是否正在运行异步操作。
 99             // 如果System.ComponentModel.BackgroundWorker正在运行异步操作,则为true;否则为false。
100             while (bw.IsBusy);
101         }
102     }
103 }

3、运维该调整台应用程序,运转效果(每一遍运转效果可能分歧)如下图所示:

图片 3

   在第68行代码处,我们创立了三个BackgroundWorker组件的实例,并且在第71行代码和第74行代码处分明地表明该实例扶植进程更新和异步撤废操作。

  在第77行代码、第79行代码和第81行代码处,我们给该实例挂载了四个事件管理方法。每当DoWork、ProgressChanged和RunWorkerCompleted事件时有发生时,都会实践相应的“WorkerDoWork方法”、“WorkerProgressChanged”方法和“WorkerCompleted”方法。

  其余代码请参见注释。

  源码下载

在上一篇C#二十四线程之线程池篇第22中学,我们最首要学习了线程池和并行度以至哪些兑现打消选项的相干知识。在这里一篇中,...

接上文 二十多线程编程学习笔记——线程池

  • 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模型。
  • 如非必需,不要手动设置线程池的最小线程数和最大线程数,CL科雷傲会自动的进行线程池的扩展和缩小,手动干预往往让品质更差。

线程池还应该有二个ThreadPool.RegisterWaitForSingleObject,那么些方法允许大家将回调函数放入线程池中的队列中。当提供的守候事件管理器接收到功率信号或产生超时时,那几个回调函数将被调用,这样就落到实处了为线程池中操作达成超时操作。

1.2 在线程池中调用委托

本节展现的是何许在线程池中如何异步的推行委托,然后将介绍贰个叫异步编制程序模型(Asynchronous Programming Model,简称APM)的异步编制程序格局。

在本节及随后,为了收缩代码量,在援引程序集评释地方默许增多了using static System.Consoleusing static System.Threading.Thead宣示,那样评释能够让大家在前后相继中一丢丢一些含义相当小的调用语句。

演示代码如下所示,使用了平凡创造线程和APM格局来实践同多个任务。

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的线程也是线程池的做事线程。

图片 4

在上文中,使用BeginOperationName/EndOperationName方法和.Net中的IAsyncResult指标的措施被叫作异步编制程序模型(或APM情势),那样的艺术被称作异步方法。使用委托的BeginInvoke形式来运作该信托,BeginInvoke收到一个回调函数,该回调函数会在职责管理完结后背调用,並且能够传递贰个顾客自定义的情事给回调函数。

后天这种APM编制程序形式用的更加少了,更推荐应用任务并行库(Task Parallel Library,简称TPL)来集团异步API。

1.代码之类:

1.3 向线程池中放入异步操作

本节将介绍怎么着将异步操作放入线程池中实行,何况怎样传递参数给线程池中的线程。本节中主要性行使的是ThreadPool.QueueUserWorkItem()办法,该办法可将索要周转的职务通过委托的款型传递给线程池中的线程,并且同意传递参数。

动用比较轻易,演示代码如下所示。演示了线程池使用中哪些传递格局和参数,最终索要注意的是采取了Lambda表明式和它的闭包机制。

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));
}

运维结果如下图所示。

图片 5

using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;using System.Text;using System.Threading;namespace ThreadTPLDemo{    class Program    {           static void Main(string[] args)        {            Console.WriteLine("开始测试线程池中定时运行操作。。。");            TimesOperation(TimeSpan.FromSeconds(5));//提供了5秒的操作时间,会超时            TimesOperation(TimeSpan.FromSeconds(9));//提供了9秒的操作时间,正常工作            Console.WriteLine("。。。。。。。。。。。。。。。。");            Console.Read();        }        private static void  TimesOperation(TimeSpan  workTimes)        {            using (var manuEvt=new ManualResetEvent(false))            {                using (var cts=new CancellationTokenSource                {                    Console.WriteLine("开始--线程池中的定时操作。。。");                    var work = ThreadPool.RegisterWaitForSingleObject                        (manuEvt, (state, isTimeOut) => AsyncOperWait(cts, isTimeOut), null, workTimes, true);                    Console.WriteLine("一个长时间运行的线程操作。。。");                    ThreadPool.QueueUserWorkItem(_ => AsyncOper(cts.Token, manuEvt));                    Console.WriteLine("。。。间隔2秒再次运行。。。");                    Thread.Sleep(workTimes.Add(TimeSpan.FromSeconds(2)));                    work.Unregister;                }            }         }        private static void AsyncOper(CancellationToken token,ManualResetEvent mrevt)        {            Console.WriteLine("开始--线程池中的第一个工作线程。。。");            for (int i = 0; i < 7; i  )            {                if (token.IsCancellationRequested)//判断是否已经取消操作                {                    Console.WriteLine("使用轮询方法取消工作线程  ID:{0}", Thread.CurrentThread.ManagedThreadId);                    return;                }                Thread.Sleep(TimeSpan.FromSeconds(1));            }            mrevt.Set();            Console.WriteLine("-------线程池中的第一个工作线程 发出信号----------");        }        private static void AsyncOperWait(CancellationTokenSource cts, bool isTimeOut)        {            Console.WriteLine("开始--线程池中的第二个工作线程。。。");            if (isTimeOut)//判断是否已经取消操作            {                cts.Cancel();                Console.WriteLine("工作线程已经超时,并取消。  ID:{0}", Thread.CurrentThread.ManagedThreadId);            }            else            {                Console.WriteLine("-------线程池中的第二个工作线程 工作完成----------");            }                        }    }} 

1.4 线程池与并行度

在本节中,首即便接纳普通成立线程和使用线程池内的线程在任务量十分的大的情形下有啥分化,咱们模拟了三个面貌,创设了很多不一的线程,然后分别采纳普通创制线程格局和线程池格局看看有如何分歧。

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秒钟,可是只创设了非常少的线程,为操作系统节省了线程和内部存款和储蓄器空间,但花了越多的时光。

图片 6

2.前后相继结果如下。

1.5 达成一个收回选项

在从前的篇章中有涉及,固然必要结束叁个线程的试行,那么能够行使Abort()格局,然而有为数不菲的来由并不推荐使用Abort()方法。

此地推荐的形式是采纳合作式撤销(cooperative cancellation),那是一种保障的技艺来安全撤销不再要求的义务。其关键运用CancellationTokenSourceCancellationToken两个类,具体用法见上边演示代码。

以下延时期码首即使促成了使用CancellationTokenCancellationTokenSource来贯彻职务的吊销。不过职责撤销后能够张开三种操作,分别是:间接回到、抛出ThrowIfCancellationRequesed格外和执行回调。详细请看代码。

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("第三个任务运行完成.");
}

运行结果如下所示,切合预期结果。

图片 7

图片 8

1.6 在线程池中选取等待事件管理器及逾期

本节将介绍如何在线程池中选取等待义务和怎么开展过期管理,在那之中重大选取ThreadPool.RegisterWaitForSingleObject()方式,该格局允许传入三个WaitHandle对象,和内需施行的职分、超时时间等。通过选拔那些方法,可实现线程池意况下对过期职务的管理。

亲自去做代码如下所示,运转了四遍使用ThreadPool.RegisterWaitForSingleObject()编纂超时期码的RunOperations()方法,但是所传诵的逾期时间不一,所以导致二个自然超时和贰个不会晚点的结果。

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("工作操作成功.");
    }
}

运作结果如下图所示,与预期结果相符。

图片 9

程序运营以往按顺序归入了一部分长日子运作的操作,那个操作运维6秒,要是运转成功,则会安装二个ManualResetEvent频域信号。借使撤除了那个操作,则这几个操作会被扬弃。

1.7 使用放大计时器

放大计时器是FCL提供的三个类,叫System.Threading.Timer,可要结果与创设周期性的异步操作。该类应用相比较简单。

以下的亲自去做代码应用了停车计时器,并安装了沙漏延时运转时间和周期时间。

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()方法。

图片 10

作者们还注册了第叁个异步操作,当从马努alReset伊芙nt对象中接受了三个能量信号将来,这些异步操作会被调用。倘若第七个操作顺遂进行,则会安装功率信号。要是第贰个操作施行超时,则会由此CancellationToken来裁撤第二个操作。

1.8 使用BackgroundWorker组件

本节任重(英文名:rèn zhòng)而道远介绍BackgroundWorker组件的运用,该零件实际上被用来Windows窗体应用程序(Windows Forms Application,简称 WPF)中,通过它达成的代码能够一贯与UI调节器交互,特别自认和好用。

示范代码如下所示,使用BackgroundWorker来完毕对数据开展测算,并且让其支持报告职业进程,帮衬撤销职分。

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}");
    }
}

运维结果如下所示。

图片 11

在本节中,使用了C#中的其他一个语法,叫事件(event)。当然这里的风浪不相同于在此之前在线程同步章节中涉及的平地风波,这里是阅览者设计格局的体现,包括事件源、订阅者和事件管理程序。由此,除了异步APM格局意外,还会有依附事件的异步方式(伊夫nt-based Asynchronous Pattern,简称 EAP)

注:当线程池中大量的操作被打断时,上面包车型地铁主意就特别低价了。

参照书籍

正文首要参谋了以下几本书,在这里对这一个小编表示衷心的谢谢你们提供了这么好的素材。

  1. 《CLR via C#》
  2. 《C# in Depth Third Edition》
  3. 《Essential C# 6.0》
  4. 《Multithreading with C# Cookbook Second Edition》
  5. 《C#十二线程编制程序实战》

源码下载点击链接 亲自去做源码下载

六、 使用定时器

作者水平有限,若是不当应接各位评论指正!

行使Threading.Timer对象完结线程池中的周期性调用的异步操作。

1.代码之类:

using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;using System.Text;using System.Threading; namespace ThreadPoolDemo{      class Program    {        static Timer timer;          static void Main(string[] args)        {            Console.WriteLine("开始测试线程池中通过计时器运行操作,输入A停止计时器。。。");            DateTime startTime = DateTime.Now;            timer = new Timer(_=>TimesOperation(startTime),null,TimeSpan.FromSeconds(1),TimeSpan.FromSeconds(2));            Thread.Sleep(6000);            timer.Change(TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4));             Console.WriteLine("。。。。。。。。。。。。。。。。");            ConsoleKeyInfo key = Console.ReadKey();            if (key.Key==ConsoleKey.A)            {                timer.Dispose();            }            Console.Read();        }        private static void TimesOperation(DateTime startTime)        {            TimeSpan time = DateTime.Now - startTime;            Console.WriteLine("线程 {0} 从 {1} 开始 运行了 {2} 秒",            Thread.CurrentThread.ManagedThreadId, startTime.ToString("yyyy-MM-dd HH:mm:ss"), time.Seconds);         }    }} 

2.程序运维结果如下。

图片 12

次第运维时,首先制造了叁个timer,第叁个参数是lambla表明式,将会在线程池中实行,第4个参数是null。然后调用TimerOperation方法,并给一个起来时间,并点名几时会率先次运营TimerOperation,以致随后的重复调用间距时间。

七、 使用BackgroundWorker组件

本示例使用BackgroundWorker组件达成异步操作。

前后相继运行时创制了一个BackgroundWorker对象实例,呈现的提醒那个后台专门的工作线程补助裁撤操作及操作进程通报。

1.代码之类:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Diagnostics;using System.Linq;using System.Text;using System.Threading;namespace ThreadTPLDemo{     class Program    {        static Timer timer;           static void Main(string[] args)        {            Console.WriteLine("开始测试 BackgroundWorker。。。");            BackgroundWorker bgwork = new BackgroundWorker();            bgwork.WorkerReportsProgress = true;            bgwork.WorkerSupportsCancellation = true;             bgwork.DoWork  = worker_dowork;            bgwork.ProgressChanged  = worker_ProgressChanged;            bgwork.RunWorkerCompleted  = worker_Completed;             bgwork.RunWorkerAsync();//开始后台运行                    Console.WriteLine("。。。。。输入C取消BackgroundWorker后台组件。。。。。。。。。。。");            do            {                ConsoleKeyInfo key = Console.ReadKey();                if (key.KeyChar.ToString().ToUpper() == "C")                {                    bgwork.CancelAsync();                }            } while (bgwork.IsBusy);                   Console.Read();         }        static void worker_dowork(object sender,DoWorkEventArgs e)        {                       Console.WriteLine("线程 {0}  开始 运行",            Thread.CurrentThread.ManagedThreadId);            int result = 0;            var bgwork = (BackgroundWorker)sender;            for (int i = 0; i < 100; i  )            {                if (bgwork.CancellationPending)//已经取消后台操作                {                    e.Cancel = true;                    return;                }                if (i==0)                {                    bgwork.ReportProgress;//显示进度                }                Thread.Sleep(200);                result  = i;            }            e.Result = result;        }        static void worker_ProgressChanged(object sender,ProgressChangedEventArgs e)        {            Console.WriteLine("线程 {0}   已经完成工作量的 {1} %",          Thread.CurrentThread.ManagedThreadId,e.ProgressPercentage);        }        static void worker_Completed(object sender,RunWorkerCompletedEventArgs e)        {            Console.WriteLine("线程 {0} 已经执行结束!",        Thread.CurrentThread.ManagedThreadId);            if (e.Error!=null)            {                Console.WriteLine("线程 {0} 发生错误,错误信息:{1}",     Thread.CurrentThread.ManagedThreadId,e.Error.Message);            }            else if (e.Cancelled)            {                Console.WriteLine("线程 {0} 已经取消",   Thread.CurrentThread.ManagedThreadId);            }            else            {                Console.WriteLine("线程 {0} 执行成功,结果是:{1}",   Thread.CurrentThread.ManagedThreadId, e.Result);            }        }    }}

2.顺序平常化施行实现,如下图。

图片 13

  1. 程序实践的中途,人工干预,裁撤。如下图。请看下图深黄框的岗位,小编输入了字母C,则线程被撤销。

图片 14

在前后相继中大家定义了多少个事件。

  1. DoWork事件,当八个后台职业对象通过RunWorkerAsync运行一个异步操作时,将调用那么些事件管理器。这几个事件管理器会运维在线程池中,当运维甘休,将运维结果做为参数字传送递给RunWorkerCompleted事件,同一时间触发那事件。
  2. ProgressChanged事件,通过接收BackgroundWorker的ReportProgress方法传递过来的参数,展现线程施行的速度。
  3. RunWorkerCompleted事件,在这里事件中能够知晓操作是水到渠成做到,依然时有产生了不当,或是被吊销。

本文由pc28.am发布于计算机编程,转载请注明出处:多线程之线程池篇3,多线程编程学习笔记

上一篇:关键字详解,局地类型 下一篇:没有了
猜你喜欢
热门排行
精彩图文
  • 多线程之线程池篇3,多线程编程学习笔记
    多线程之线程池篇3,多线程编程学习笔记
    目录 C#多线程之线程池篇3, 在上一篇C#四线程之线程池篇2中,大家最首要学习了线程池和并行度以致哪些贯彻裁撤选项的有关文化。在这里一篇中,大家
  • 关键字详解,局地类型
    关键字详解,局地类型
    3. 局地类型的小心点 怎么类Form1 要用partial修饰?partial主要用在哪些地方?     为何新创立的WinowsForm的类定义成了一个局地类。咱们展开Form1.Designer.
  • Redis可视化学工业具Web,redis源代码结构剖析
    Redis可视化学工业具Web,redis源代码结构剖析
    一、简介 最近因为工作需要,使用了一些单机版Redis的界面化管理工具,使用过程中那惨痛的体验真的只有用过的人才能体会;为此本人和小伙伴准备动手
  • Socket网络通讯,的区别及联系
    Socket网络通讯,的区别及联系
    互连网编制程序 应用程序可以通过 TCPClient、TCPListener 和 UDPClient 类使用传输调控协议(TCP) 和顾客数据文报公约 (UDP) 服务。那些左券类创建在System.Net.Socke
  • 字符串和字符,Python3主导数据类型
    字符串和字符,Python3主导数据类型
    Python3字符串 字符串和字符 本节内容包括: 字符串字面量 初始化空字符串 字符串可变性 字符串的值类型 使用字符 计算字符数量 连接字符串和字符 字符