五、 在线程池中使用等待事件处理器与超时
本示例主要学习如果对线程池中的操作实现超时,并在线程池中正确等待。
线程池还有一个ThreadPool.RegisterWaitForSingleObject,这个方法允许我们将回调函数放入线程池中的队列中。当提供的等待事件处理器接收到信号或发生超时时,这个回调函数将被调用,这样就实现了为线程池中操作实现超时操作。
1.代码如下:
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(manuEvt); } } } 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("-------线程池中的第二个工作线程 工作完成----------"); } } } }
2.程序结果如下。
程序启动之后按顺序放入了一些长时间运行的操作,这个操作运行6秒,如果运行成功,则会设置一个ManualResetEvent信号。如果取消了这个操作,则这个操作会被丢弃。
我们还注册了第二个异步操作,当从ManualResetEvent对象中接受了一个信号之后,这个异步操作会被调用。如果第一个操作顺利执行,则会设置信号。如果第一个操作执行超时,则会通过CancellationToken来取消第一个操作。
注:当线程池中大量的操作被阻塞时,上面的方法就非常有用了。
六、 使用计时器
使用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.程序运行结果如下。
程序启动时,首先创建了一个timer,第一个参数是lambla表达式,将会在线程池中执行,第二个参数是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%10==0) { bgwork.ReportProgress(i);//显示进度 } 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.程序正常执行结束,如下图。
3. 程序执行的中途,人工干预,取消。如下图。请看下图中黄框的位置,我输入了字母C,则线程被取消。
在程序中我们定义了三个事件。
- DoWork事件,当一个后台工作对象通过RunWorkerAsync启动一个异步操作时,将调用这个事件处理器。这个事件处理器会运行在线程池中,当运行结束,将运行结果做为参数传递给RunWorkerCompleted事件,同时触发此事件。
- ProgressChanged事件,通过接收BackgroundWorker的ReportProgress方法传递过来的参数,显示线程执行的进度。
- RunWorkerCompleted事件,在此事件中可以知道操作是成功完成,还是发生了错误,或是被取消。