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


      在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识。在这一篇中,我们主要学习如何使用等待句柄和超时、使用计时器和使用BackgroundWorker组件的相关知识。

    五、使用等待句柄和超时

      在这一小节中,我们将学习如何在线程池中实现超时和正确地实现等待。具体操作步骤如下:

    1、使用Visual Studio 2015创建一个新的控制台应用程序。

    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、运行该控制台应用程序,运行效果如下图所示:

      线程池还有另一个有用的方法:ThreadPool.RegisterWaitForSingleObject,该方法允许我们将回调方法放入线程池的队列中,当所提供的等待句柄发送信号或者超时发生时,该回调方法即被执行。这允许我们对线程池中的操作实现超时。

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

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

      在第59行代码处,我们调用ThreadPool的“QueueUserWorkItem”静态方法来执行“WorkerOperation”方法,而该方法所消耗的时间为6秒,在这六秒中内已经在线程池中发送了超时,所以会执行第13~18行和第32~35行处的代码。

      在第73行代码处,我们传递了数值7给“RunOperations”方法,设置线程池的超时时间为7秒,因为“WorkerOperation”方法的执行时间为6秒,所以在这种情况下没有发生超时,成功执行完毕“WorkerOperation”方法。

     六、使用计时器

      在这一小节中,我们将学习如何使用System.Threading.Timer对象在线程池中定期地调用一个异步操作。具体操作步骤如下所示:

    1、使用Visual Studio 2015创建一个新的控制台应用程序。

    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、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

      首先,我们创建了一个Timer实例,它的构造方法的第一个参数是一个lambda表达式,表示要在线程池中执行的代码,在该表达式中我们调用了“TimerOperation”方法,并给它提供了一个开始时间值。由于我们没有使用state对象,因此我们给Timer的构造方法的第二个参数传递了null。第三个参数表示第一次执行“TimerOperation”所要花费的时间为1秒钟。第四个参数表示每次调用“TimerOperation”之间的时间间隔为2秒钟。

      在主线程阻塞6秒钟之后,我们调用了Timer实例的“Change”方法,更改了每次调用“TimerOperation”之间的时间间隔为4秒钟。

      最后,我们等待输入“Enter”键来结束应用程序。

    七、使用BackgroundWorker组件

       在这一小节中,我们学习另外一种异步编程的方式:BackgroundWorker组件。在这个组件的帮助下,我们可以通过一系列事件和事件处理方法组织我们的异步代码。具体操作步骤如下所示:

    1、使用Visual Studio 2015创建一个新的控制台应用程序。

    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、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

       在第68行代码处,我们创建了一个BackgroundWorker组件的实例,并且在第71行代码和第74行代码处明确地说明该实例支持进度更新和异步取消操作。

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

      其他代码请参考注释。

      源码下载

  • 相关阅读:
    烦人的微软拼音
    android sdk manager 不能连接到https://dl-ssl.google.com
    js 截屏
    计算机的发展史
    python全栈课程内容
    内置函数
    mapfilter educe
    函数式编程->reduce
    函数式编程
    函数式编程->map
  • 原文地址:https://www.cnblogs.com/yonghuacui/p/6229183.html
Copyright © 2020-2023  润新知