你的位置:首页 > ASP.net教程

[ASP.net教程]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       else20       {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       try29       {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       finally38       {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”方法。

  其他代码请参考注释。

  源码下载