C# 温故而知新: 线程篇(二) 下

时间:2022-05-07
本文章向大家介绍C# 温故而知新: 线程篇(二) 下,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

首先介绍下Classic Async Pattern:

其实Classic Async Pattern指的就是我们常见的BeginXXX和EndXXX

IAsyncResult 异步设计模式通过名为 BeginOperationName 和 EndOperationName 的两个方法来实现原同步方法的异步调用

让我们再来回顾下.net中的几个的BeginXXX 和EndXXX

Stream中的BeginRead,EndRead,BeginWrite,EndWriteSocket中的BeginReceive,EndReceiveHttpWebRequest的BeginGetRequestStream和EndGetRequestStream....

再来介绍下Event-based Async Pattern

Event-based Async Pattern 值的是类似于 xxxxxxxAsync() 和 类似于event xxxxxCompleteHander

通过一个方法和一个完成事件来处理异步操作

.net中的例子:

WebClient.DownloadStringAsync(string uri)和 event DownloadStringCompleteEventHandler

其实Classic Async Pattern和Event-based Async Pattern都是一种异步的设计思路,我们也可以根据这一系列的

思路去实现自己的异步方法

7 异步线程的发展趋势以及.net4.5中异步的简化

微软貌似现在把精力放在win8或WinPhone的metro上,而且记得在win 8开发者培训的会议上,着重阐述了微软对于异步的支持将越来越强,而且为了快

速响应诸如移动设备的应用程序,微软也在争取为每个方法都实现一个异步版本…..可见异步的重要性,相信异步的发展趋势是个不错的

上升曲线,还没反应过来.net4.5的异步新特性便诞生了。首先经历过异步摧残的我们,都会有这样一个感受,往往回调方法和普通方法

会搞错,在复杂的项目面前,有时候简直无法维护,到处都是回调函数,眼花缭乱 所以微软为了简化异步的实现过程,甚至大刀阔斧将

回调函数做成看起来像同步方法,虽然觉得很诡异,还是让我们初步了解下这种异步的新特性

先看代码

/// <summary>
        /// .net 4.5 中 async 和 await 全新的关键字 一起实现异步的简化
        /// </summary>
        void async ShowUriContent(string uri)
        {
            using (FileStream fs = File.OpenRead("你的文件地址"))
            {
                using (FileStream fs2 = new FileStream())
                {
                    byte[] buffer = new byte[4096];
                    //FileStream的ReadAsync方法也是net4.5版本出现的,它返回一个Task<int>对象
                    //而且作用于await后的异步代码会等待阻塞直到异步方法完成后返回
                    int fileBytesLength = await fs.ReadAsync(buffer,0,buffer.Length).ConfigureAwait(false);
                    while(fileBytesLength>0)
                    {
                           //FileStream的WriteAsync方法也是net4.5版本出现的
                           await fs2.WriteAsync(buffer,0,buffer.Length).ConfigureAwait(false);      
                    }             
                }
            }
        }

相信看完代码后大家有耳目一新的感觉,不错,原本异步调用的回调函数不见了,取而代之的是await和方法声明上的async关键字,新特性允许

我们实现这俩个关键字后便能在方法中实现“同步方式”的异步方法,其实这解决了一些棘手的问题,诸如原本需要在回调事件里才能释放的文件句

柄在这里和同步方法一样,使用using便搞定了,还有截获异常等等,都不用像之前那样痛苦了,这里还有一些东东需要关注下,大家先不用去深

究ConfigureAwait这个方法,由于ReadAsync和 WriteAsync方法是.net 4.5新加的属于返回Task<int>类型的方法所以使用ConfigureAwait

方法能够将数值取到,关于Task泛型类我会在今后的章节中详细阐述

8 本章示例

自定义一个简单的线程池

static void Main(string[] args)
        {
            ThreadStart[] startArray =
            {
              new ThreadStart(()=>{
                  Console.WriteLine("第一个任务"); 
              }),
              new ThreadStart(()=>{Console.WriteLine("第二个任务");}),
              new ThreadStart(()=>{Console.WriteLine("第三个任务");}),
              new ThreadStart(()=>{Console.WriteLine("第四个任务");}),
            };
            MyThreadPool.SetMaxWorkThreadCount(2);
            MyThreadPool.MyQueueUserWorkItem(startArray);
            Console.ReadKey();
        }


       /// <summary>
       /// 自定义一个简单的线程池,该线程池实现了默认开启线程数
       /// 当最大线程数全部在繁忙时,循环等待,只到至少一个线程空闲为止
       /// 本示例使用BackgroundWorker模拟后台线程,任务将自动进入队列和离开
       /// 队列
       /// </summary>
        sealed class MyThreadPool
        {
            //线程锁对象
            private static object lockObj = new object();
            //任务队列
            private static Queue<ThreadStart> threadStartQueue = new Queue<ThreadStart>();
            //记录当前工作的任务集合,从中可以判断当前工作线程使用数,如果使用int判断的话可能会有问题,
            //用集合的话还能取得对象的引用,比较好
            private static HashSet<ThreadStart> threadsWorker = new HashSet<ThreadStart>();
            //当前允许最大工作线程数
            private static int maxThreadWorkerCount = 1;
            //当前允许最小工作线程数
            private static int minThreadWorkerCount = 0;


            /// <summary>
            /// 设定最大工作线程数
            /// </summary>
            /// <param name="maxThreadCount">数量</param>
            public static void SetMaxWorkThreadCount(int maxThreadCount) 
            {
                maxThreadWorkerCount =minThreadWorkerCount>maxThreadCount?
                minThreadWorkerCount : maxThreadCount;
            }
            /// <summary>
            /// 设定最小工作线程数
            /// </summary>
            /// <param name="maxThreadCount">数量</param>
            public static void SetMinWorkThreadCount(int minThreadCount)
            {
                minThreadWorkerCount = minThreadCount > maxThreadWorkerCount ?
                maxThreadWorkerCount : minThreadCount;
            }
            /// <summary>
            /// 启动线程池工作
            /// </summary>
            /// <param name="threadStartArray">任务数组</param>
            public static void MyQueueUserWorkItem(ThreadStart[] threadStartArray)
            {
                //将任务集合都放入到线程池中
                AddAllThreadsToPool(threadStartArray);
                //线程池执行任务
                ExcuteTask();
            }
            /// <summary>
            /// 将单一任务加入队列中
            /// </summary>
            /// <param name="ts">单一任务对象</param>
            private static void AddThreadToQueue(ThreadStart ts)
            {
                lock (lockObj)
                {
                    threadStartQueue.Enqueue(ts);
                }
            }


            /// <summary>
            /// 将多个任务加入到线程池的任务队列中
            /// </summary>
            /// <param name="threadStartArray">多个任务</param>
            private static void AddAllThreadsToPool(ThreadStart[] threadStartArray)
            {
                foreach (var threadStart in threadStartArray)
                    AddThreadToQueue(threadStart);
            }


            /// <summary>
            /// 执行任务,判断队列中的任务数量是否大于0,如果是则判断当前正在使用的工作线程的
            /// 数量是否大于等于允许的最大工作线程数,如果一旦有线程空闲的话
            /// 就会执行ExcuteTaskInQueen方法处理任务
            /// </summary>
            private static void ExcuteTask()
            {
                while (threadStartQueue.Count > 0)
                {
                    if (threadsWorker.Count < maxThreadWorkerCount)
                    {
                        ExcuteTaskInQueen();
                    }
                }
            }


            /// <summary>
            /// 执行出对列的任务,加锁保护
            /// </summary>
            private static void ExcuteTaskInQueen()
            {
                lock (lockObj)
                {
                    ExcuteTaskByThread(
threadStartQueue.Dequeue());
                }
            }


            /// <summary>
            /// 实现细节,这里使用BackGroudWork来实现后台线程
            /// 注册doWork和Completed事件,当执行一个任务前,前将任务加入到
            /// 工作任务集合(表示工作线程少了一个空闲),一旦RunWorkerCompleted事件被触发则将任务从工作
            /// 任务集合中移除(表示工作线程也空闲了一个)
            /// </summary>
            /// <param name="threadStart"></param>
            private static void ExcuteTaskByThread(ThreadStart threadStart)
            {
                threadsWorker.Add(threadStart);
                BackgroundWorker worker = new BackgroundWorker();
                worker.DoWork += (o, e) => { threadStart.Invoke(); };
                worker.RunWorkerCompleted += (o, e) => { threadsWorker.Remove(threadStart); };
                worker.RunWorkerAsync();
            }
        }

显示结果:

Asp.net异步IHttpAsyncHandler示例

有时我们需要使用IHttpAsyncHandler来异步实现一些特定的功能,让我用很简单的示例来阐述这个过程

1:首先编写Handler1的逻辑,注意要继承IHttpAsyncHandler接口

/// <summary>
    /// 异步IHttpHandler,实现了一个简单的统计流量的功能,
    /// 由于是示例代码,所以没有判断IP或者MAC
    /// </summary>
    public class Handler1 : IHttpAsyncHandler
    {
        //默认访问量是0
        static int visitCount = 0;
        /// <summary>
        /// 这个HttpHandler的同步方法
        /// </summary>


        /// <param name="context"></param>
        public void ProcessRequest(HttpContext context)
        {
        }


        public bool IsReusable
        {
            get
            {
                return false;
            }
        }


        /// <summary>
        /// 实现IHttpAsyncHandler 接口方法
        /// </summary>
        /// <param name="context">当前HttpContext</param>
        /// <param name="cb">回调函数</param>
        /// <param name="extraData"></param>
        /// <returns></returns>
        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            //这里可以加上判断IP或mac的方法
            visitCount++;
            //实例化AsyncUserVisiteCounterResult对象
            AsyncUserVisiteCounterResult result = new AsyncUserVisiteCounterResult(cb, visitCount, context);
            result.Display();
            return result;
        }


       /// <summary>
        ///  结束本次IHttpAsyncHandler工作时触发的request方法
       /// </summary>
       /// <param name="result"></param>
        public void EndProcessRequest(IAsyncResult result)
        {
 
        }
    }


    /// <summary>
    /// 自定义IAsyncResult 实现我们额外的Display方法
    /// </summary>
    public class AsyncUserVisiteCounterResult : IAsyncResult
    {
        //回调参数
        private object _param;
        //是否异步执行完成
        private bool _asyncIsComplete;
        //回调方法
        private AsyncCallback _callBack;
        //当前上下文
        private HttpContext _context;


        public AsyncUserVisiteCounterResult(AsyncCallback callBack, object stateParam, HttpContext context)
        {
            this._callBack = callBack;
            this._param = stateParam;
            _asyncIsComplete = false;
            this._context = context;
        }




        public object AsyncState
        {
            get { return this._param; }
        }


        /// <summary>
        /// 等待句柄用于同步功能,关于等待句柄会在后续章节陈述
/// </summary>
        public System.Threading.WaitHandle AsyncWaitHandle
        {
            get
            {
                return null;
            }
        }


        /// <summary>
        /// 该属性表示不需要异步任务同步完成
        /// </summary>
        public bool CompletedSynchronously
        {
            get { return false; }
        }
        /// <summary>
        /// 该属性表示异步任务是否已经执行完成
        /// </summary>
        public bool IsCompleted
        {
            get
            {
                return this._asyncIsComplete;
            }
        }


        /// <summary>
        /// 自定义的额外功能,需要注意的是,执行完异步功能后
        /// 要将_asyncIsComplete设置为true表示任务执行完毕而且
        /// 执行回调方法,否则异步工作无法结束页面会卡死
        /// </summary>
        public void Display()
        {
            //这里先不用waitHandle句柄来实现同步
            lock (this)
            {
                this._context.Response.Write("你是第" + (int)this._param + "位访问者,访问时间:"+DateTime.Now.ToString());
                this._asyncIsComplete = true;
                this._callBack(this);
            }
        }
    }

2 在web.config中添加相应的配置,注意path指的是.ashx所在的路径,指的是相应的文件类型

    <httpHandlers>
      <add verb="*" path="AsyncThreadInAsp.net.Handler1.ashx" type="AsyncThreadInAsp.net.Handler1"/>
    </httpHandlers>

3 最后在页面中访问

9 本章总结

本章详细介绍了CLR线程池和异步线程的一些概念和使用方法,包括线程池的优点和细节,异步的执行过程和重要元素等等,在下一章节中