多线程编程之计算限制型异步操作

1. CLR线程池简介

1.1  CLR为什么支持线程池

1.2 线程池ThreadPool管理线程 

2.  线程执行上下文

2.1 线程执行上下文简介

2.2  一个简单示例

3. 线程池常见应用情景示例

3.1 将一个线程添加至线程池中(向线程传递参数) 

3.2 协作式取消 

4. Task简介

5. Task编程基础 

6. 定时器Timer 

<1>. CLR线程池简介

1.1 CLR为什么支持线程池?

上一篇中讲到如果在一个应用程序中启动了多个线程的话,显然是会影响到程序的运行效率,一种很直观的想法是:如果一个线程完成了任务,我们并不在内存中销毁这个线程的实例,而是将这个线程进入空闲状态,如果又来了一个请求,可以不用另外创建新线程,这样显然能够带来性能上的提升,在.net中,CLR为我们实现了线程池,它可以看作是线程的一个集合。每个CLR都存在一个全局的线程池,供所有的AppDomain共享。

1.2 线程池ThreadPool管理线程

 System.Threading.ThreadPool中提供了查询和设置线程池性质的的静态方法。

public static bool SetMaxThreads(int workerThreads, int completionPortThreads);  // 设置线程池中线程数量的最大值,但是永远不要折磨做,容易发生死锁等情况

public static bool SetMinThreads(int workerThreads, int completionPortThreads);  // 设置线程池中线程数量的最小值

public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads);  // 返回线程池支持的最大线程数和当前活动的线程的差值

<2>. 线程执行的上下文及其流转

2.1 线程执行上下文简介 

线程的执行上下文包含安全设置,宿主设置,线程上下文数据(System.Runtime.Remoting.Messaging.CallContext的LogicalSetData方法)等信息。当一个线程使用另外的线程执行任务时,前者的线程执行上下文将流转至后者的线程执行上下文中。在c#中类型System.Threading.ExecutionContext来控制线程执行上下文的流转:

public static AsyncFlowControl SuppressFlow();  // 阻止( Suppress本意是抑制)线程执行上下文的流转

public static bool IsFlowSuppressed();  // 判断当先线程执行上下文是否被阻止

2.2 一个简单示例

 static void Main(string[] args)
        {
            // 见一些数据保存在Main线程的执行上下文中
            CallContext.LogicalSetData("myname", "xuqiang");
            // 线程执行上下文流转,所以这里是能够得到myname的值的
            ThreadPool.QueueUserWorkItem
            (
                state =>
                {
                    // 将输出xuqiang
                    Console.WriteLine("MyName :" + CallContext.LogicalGetData("myname"));  
                }
            );
            // 现在阻止线程上下文的流动
            ExecutionContext.SuppressFlow();
            // 下面的线程将无法访问Main线程的上下文
            ThreadPool.QueueUserWorkItem
            (
                state =>
                {
                    // 不会输出xuqiang
                    Console.WriteLine("MyName :" + CallContext.LogicalGetData("myname"));
                }
            );
            // 回复上下文流转
            ExecutionContext.RestoreFlow();
            // 等待线程执行完毕
            Thread.Sleep(1000);
            Console.WriteLine("Press any key...");
            Console.ReadKey();

        } 

通过上面的建立示例,我们能够明确,

1. 如何向线程池中添加工作线程: 

通过调用QueueUserWorkItem,该静态方法存在两个重载方法:

public static bool QueueUserWorkItem(WaitCallback callBack);  // 不需要传递参数

public static bool QueueUserWorkItem(WaitCallback callBack, object state);  // 需要向线程传递参数 

2. 通过函数SuppressFlown能够阻止线程上下文的流转,通过RestoreFlow能够恢复线程上下文

3. 通过Thread.Sleep函数能够阻塞调用Thread.Sleep的线程(这里是指Main线程) 

<3>. 线程池常见应用场景示例

3.1 将一个线程添加至线程池中(向线程中传递参数)

  static void Main(string[] args)
        {
            Console.WriteLine("Main thread: queuing an asychronous operation");
            // 向线程池中添加一个工作线程,同时传递参数5
            ThreadPool.QueueUserWorkItem(ComputeOp, 5);
            Console.WriteLine("Main thread: Doing other work here..");
            
            // 将main线程休眠一段时间,以保证computeOp执行完成
            Thread.Sleep(10000);    // 10秒
            Console.WriteLine("Hit <Enter> to end this program");
            Console.ReadKey();
        }
        private static void ComputeOp(object state)
        {
            Console.WriteLine("CompiteOp: state is " + state);
            
            // 模拟工作1s
            Thread.Sleep(1000);
            // 这个线程返回到线程池中,将等待另外的任务

        } 

3.2 协作式取消

之所以称之为“协作式取消”,是讲一个线程T如果想要获得被取消的能力的话,那么T需要接受一个类型为CancellationToken参数(该类型存在于.net 4.0中,3.5中不存在),在T中通过查询CancellationToken的IsCancellationRequested属性来判断是否已经被取消。下面结合这个场景来分析这个过程中使用到的类CancellationTokenSource和CancellationToken:

public sealed class CancellationTokenSource : IDisposable

{

public CancellationTokenSource();  // 默认构造函数

public bool IsCancellationRequested { get; }  // 查询线程是否已经被取消

public CancellationToken Token { get; }  // 构造好了CancellationTokenSource通过该方法得到CancellationToken实例,然后传递给需要实现协作式取消的线程T

public void Cancel();  // 通过该方法取消T线程

// 取消线程T的重载版本,可以通过CancellationTokenSource的CancellationToken属性的Register注册委托,在Cancel方法调用后执行。如

// 果throwOnFirstException为true的话,那么在注册的方法中的任何一个方法抛出未处理异常,Cancel将抛出该异常 ,同时阻止其他回调方法

//  的调用,如果throwOnFirstException为false的话,如果在任何的回调方法中抛出异常不会影响到其他回调方法的调用,未处理的异常将添加

// 至AggregateException中

public void Cancel(bool throwOnFirstException); 

public void Dispose();  // 实现IDisposable接口

// 连接另外一组 CancellationToken来创建CancellationTokenSource示例,例如如果通过var cts  = CreateLinkedTokenSource(token1, token2)

// 那么如果token1或者token2取消,cts将被取消

public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); 

// 重载版本 

public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2);

 public struct CancellationToken

{

public bool IsCancellationRequested { get; } // 得到是否已经存在对于该CancellationToken的取消操作

public static CancellationToken None { get; }  // 如果一个工作进程不想被取消的话,可以使用该静态字段

// 下面的几个方法将向 CancellationToken中注册委托时间,如果CancellationToken被取消,将调用这些方法

public CancellationTokenRegistration Register(Action callback); 

public CancellationTokenRegistration Register(Action<object> callback, object state); 

// 该方法主要解决在winform,wpf等中,工作线程如何更新ui线程 

public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext); 

.... 

这个示例演示了如何使用CancellationTokenSource,并向其中注册回调方法,如何管理异常等特性:

static void Main(string[] args)
        {
            Go();
        }
        public static void Go()
        {
            // 生成CancellationTokenSource对象
            CancellationTokenSource cts = new CancellationTokenSource();
            // 向cts中添加回调事件,但token调用cancel方法时,将调用这个方法
            cts.Token.Register( WhenCancel1 );
            // 将抛出异常
            cts.Token.Register(WhenCancel2 );
            cts.Token.Register(WhenCancel3);
            // 向线程队列中添加一个工作线程
            ThreadPool.QueueUserWorkItem
            (
                 o => Count(cts.Token, 1000)
            );
            Console.WriteLine("Press enter to cancel the operation .");
            Console.ReadLine();
            try
            {
                // 如果为true的话,将仅仅调用WhenCancel3方法
                // 如果为false将调用这三个回调方法
                 cts.Cancel(true);
            }
            catch (AggregateException ex)
            {
             Console.WriteLine(ex.InnerException.ToString());
            }
           
        }
        private static void WhenCancel1()
        {
            Console.WriteLine("When cancel 1 delegate called.");
        }
        
         private static void WhenCancel2()
        {
           // Console.WriteLine("When cancel 3 delegate called.");
             throw new Exception();
        }
         private static void WhenCancel3()
        {
            Console.WriteLine("When cancel 3 delegate called.");
        }
        private static void Count(CancellationToken token, Int32 countTo)
        {
            for (Int32 count = 0; count < countTo; ++count )
            {
                // 如果被取消 
                if(token.IsCancellationRequested)
                {
                    Console.WriteLine("The operation is cancel.");
                    break;
                }
                // 打印
                Console.WriteLine(count);
                // 休眠该线程一段时间
                Thread.Sleep(200);  
            }
            Console.WriteLine("Count is done.");

        } 

 下面的示例将演示如何实现一个链接的CancellationTokenSource

public static void Go()
        {
            // 生成CancellationTokenSource对象
            CancellationTokenSource cts1 = new CancellationTokenSource();
            cts1.Token.Register(() => Console.WriteLine("cts1 is cancel"));
            CancellationTokenSource cts2 = new CancellationTokenSource();
            cts2.Token.Register(() => Console.WriteLine("cts2 is cancel"));
            // 创建链接
            CancellationTokenSource linkedCts = 
                CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
    
            // 如果取消cts1或者是cts2中的一个,那么linkedCts将被取消
            cts1.Cancel();
            Console.WriteLine("cts1 cancel = {0}, cts2 cancel = {1}",
                cts1.IsCancellationRequested,
                cts2.IsCancellationRequested);
            Console.WriteLine("Press enter to cancel the operation .");
            Console.ReadLine();

        }     

 下一篇继续... 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值