首页 > 技术文章 > Task、Task返回值、多线程异常处理、任务取消、临时变量、async和await、定时器、各种锁、Parallel和PLinq并行编程、TaskSchedule、多线程模型TAP、CPU过曝死锁内存暴增

yangmengke2018 2020-01-28 13:25 原文

  1.进程、线程、多线程、计算机概念

  【进程】:一个程序运行时占用的全部计算资源的总和  【线程】:程序执行流最小单位,依托于进程,一个进程可以包含多个线程

  【多线程】:多个执行流同时运行,主要分为两种:其一CPU运算太快啦,分时间片——上下文切换(加载环境—计算—保存环境)  微观角度讲,一个核同一时刻只能执行一个线程;宏观的讲是多线程并发。其二,多CPU多核,可以独立工作,4核8线程——核指物理的核,线程指虚拟核

  多线程三大特点:不卡主线程、速度快、无序性     【Thread】:C#语言对线程对象的封装

2.同步与异步      【同步】:完成计算之后,再进入下一行,会阻塞     【异步】:不会等待方法的完成,直接进入下一行,不会阻塞

Action<string> action = this.DoSomethingLong;  //带一个string参数的耗时方法
action.Invoke("btnAsync_Click_1");   //同步
action("btnAsync_Click_2");  //同步
action.BeginInvoke("btnAsync_Click_3", null, null);   //异步
同步与异步

3.不同C#版本线程的进化升级

C#1.0 Thread 线程等待、回调、前后台线程,可以扩展Thread封装回调,各种API函数很多,可设置线程优先级,各种API包括:Start(),Suspend(),Resume(),Abort(),Thread.ResetAbort(),Join(),
C#2.0 ThreadPool 线程池使用,设置线程池,等待可用ManualResetEvent,砍掉了很多API,可以设置线程池的最大最小数量
C#3.0 Task  

4.并发编程

  并发编程包括【多线程】、【并行处理】、【异步编程】、【响应式编程】

5.Task启动

  5.1为什么要有task

    Task  = Thread + ThreadPool

    Thread:容易造成时间+空间开销,而且使用不当,容易造成线程过多,导致时间片切换...

    ThreadPool:控制能力比较弱,做thread的延续、阻塞、取消、超时等功能较弱,ThreadPool的控制权在CLR而不在编程者...

    Task看起来更像是一个Thread...但是在ThreadPool的基础上进行的封装

    .net4.0之后微软极力推荐使用task进行异步运算

  引用using System.Threading  C#3.0才有、基于ThreadPool

  WinDbg: .loadby sos clr       !threads

        private void DoSomethingLong(string name)
        {
            Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
            long lResult = 0;
            for (int i = 0; i < 1000000000; i++)
            {
                lResult += i;
            }
            Console.WriteLine($"****************DoSomethingLong {name}   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
        }
耗时的方法,无返回值,一个string输入参数
1 Task.Run(() => this.DoSomethingLong("btnTask_Click1"));
2 Task.Run(() => this.DoSomethingLong("btnTask_Click2"));
使用Task Run(Action action)运行一个新线程
TaskFactory taskFactory = Task.Factory;//4.0
taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click3"));
使用TaskFactory.StartNew(Action action)开启一个新线程
new Task(() => this.DoSomethingLong("btnTask_Click4")).Start();
直接new并start一个新线程

  以上三种启动方式并无区别,都是返回一个Task

  Run(Action)不具有参数,没有返回值

            Task.Run(() =>
            {
                string name = "无名方法";
                Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
                long lResult = 0;
                for (int i = 0; i < 1000000000; i++)
                {
                    lResult += i;
                }
                Console.WriteLine($"****************DoSomethingLong {name}   End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
            });
理解Run(Action)
            Console.WriteLine($"这是Task1单击事件Start       当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
            Task.Run(()=>
            {
                Console.WriteLine($"这是Tsak.Run(Action)无名方法Start      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                Random random = new Random();
                Thread.Sleep(random.Next(1000,5000));
                Console.WriteLine($"这是Tsak.Run(Action)无名方法End      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
            });
            Console.WriteLine($"这是Task1单击事件End      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
理解Task.Run(Action)

6.多线程阻塞-----Task.WaitAll

  设置一场景,睡觉之后要吃饭

  6.1不阻塞多线程,就会出现还没睡醒就吃饭的情况

            List<Task> listTask = new List<Task>();
            listTask.Add(Task.Run(()=> SleepMethod("杨三少")));
            listTask.Add(Task.Run(() => SleepMethod("牛大帅")));
            listTask.Add(Task.Run(() => SleepMethod("猪宝宝")));
            Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】    时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
        
不阻塞多线程

    6.2阻塞多线程,使用Task.WaitAll(Task[]),会阻塞当前线程,等着全部任务都完成后,才进入下一行

            List<Task> listTask = new List<Task>();
            listTask.Add(Task.Run(()=> SleepMethod("杨三少")));
            listTask.Add(Task.Run(() => SleepMethod("牛大帅")));
            listTask.Add(Task.Run(() => SleepMethod("猪宝宝")));
            Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】
            Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】    时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
使用Task.WaitAll(Task[])阻塞多线程

    6.2.1使用Task.WaitAll如果想不阻塞线程,可以使用Task套用Task

            Task.Run(() =>
            {//Task套Task,可以不阻塞
                List<Task> listTask = new List<Task>();
                listTask.Add(Task.Run(() => DoSomethingLong("杨三少")));
                listTask.Add(Task.Run(() => DoSomethingLong("牛大帅")));
                listTask.Add(Task.Run(() => DoSomethingLong("猪宝宝")));
                Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】
                Console.WriteLine($"全部都醒了     当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】  时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
            });
Task套用Task

   6.3设置最多等待时长,设置最长等待时间,可以看到,杨三少还未睡醒就开吃,谁让杨三少睡的太久了呢,哈哈

            List<Task> listTask = new List<Task>();
            listTask.Add(Task.Run(()=> SleepMethod("杨三少")));
            listTask.Add(Task.Run(() => SleepMethod("牛大帅")));
            listTask.Add(Task.Run(() => SleepMethod("猪宝宝")));
            //Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】
            Task.WaitAll(listTask.ToArray(),2500);//最多等待2.5秒,超过2.5秒就不再进行等待
            Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】    时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
Task.WaitAll(Task[],int MaxTime)

   6.4等待汇总

7.多线程阻塞-----Task.WaitAny

            List<Task> listTask = new List<Task>();
            listTask.Add(Task.Run(()=> SleepMethod("杨三少")));
            listTask.Add(Task.Run(() => SleepMethod("牛大帅")));
            listTask.Add(Task.Run(() => SleepMethod("猪宝宝")));
            //Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】
            //Task.WaitAll(listTask.ToArray(),2500);//最多等待2.5秒,超过2.5秒就不再进行等待
            Task.WaitAny(listTask.ToArray());//阻塞线程,只要只要有一个宝宝睡醒就开吃,“早起的宝宝有饭吃”
            Console.WriteLine($"宝宝们,睡眠结束,开始进餐啦      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】    时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
Task.WaitAny(listTask.ToArray())

“这次是杨三少醒的最早,所以他就可以先吃辣”

8.多线程不阻塞----Task.WhenAll(Task[]).ContinueWith()     Task.WhenAny(Task[]).ContinueWith() 

            List<Task> listTask = new List<Task>();
            listTask.Add(Task.Run(()=> DoSomethingLong("杨三少")));
            listTask.Add(Task.Run(() => DoSomethingLong("牛大帅")));
            listTask.Add(Task.Run(() => DoSomethingLong("猪宝宝")));
            //Task.WaitAll(listTask.ToArray());//【阻塞在此!!!】
            //Task.WaitAll(listTask.ToArray(),2500);//最多等待2.5秒,超过2.5秒就不再进行等待
            //Task.WaitAny(listTask.ToArray());//阻塞线程,只要只要有一个宝宝睡醒就开吃,“早起的宝宝有饭吃”
            Task.WhenAny(listTask.ToArray()).ContinueWith(t=>
            { 
                Console.WriteLine($"第一个已经醒了     当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】    时间:{DateTime.Now.ToString("HH:mm:ss.fff")}"); 
            });
            Task.WhenAll(listTask.ToArray()).ContinueWith(t=>
            {
                Console.WriteLine($"全部都醒了     当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】  时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
            });
Task多线程不阻塞

   ContinueWith类似于一个回调式的,ContinueWith里面的t就是前面的Task,也就是使用ContinueWith相当于一个回调,当执行了这个Task之后会自动执行ContinueWith里的后续方法。

9.仅用11个线程完成10000个任务

                List<int> list = new List<int>();
                for (int i = 0; i < 10000; i++)
                {
                    list.Add(i);
                }
                //完成10000个任务  但是只要11个线程  
                Action<int> action = i =>
                {
                    Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString("00"));
                    Thread.Sleep(new Random(i).Next(100, 300));
                };
                List<Task> taskList = new List<Task>();
                foreach (var i in list)
                {
                    int k = i;
                    taskList.Add(Task.Run(() => action.Invoke(k)));
                    if (taskList.Count > 10)
                    {
                        Task.WaitAny(taskList.ToArray());//只要有一个完成,重新整理集合
                        taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList();
                    }
                }
                Task.WhenAll(taskList.ToArray());
完成10000个线程仅用10个线程

10.使用TaskFactory可以知道已完成线程的标识

                TaskFactory taskFactory = new TaskFactory();
                List<Task> taskList = new List<Task>();
                taskList.Add(taskFactory.StartNew(o => DoSomethingLong("一一一"), "一一一"));
                taskList.Add(taskFactory.StartNew(o => DoSomethingLong("二二二"), "二二二"));
                taskList.Add(taskFactory.StartNew(o => DoSomethingLong("三三三"), "三三三"));
                taskFactory.ContinueWhenAny(taskList.ToArray(), t =>
                 {
                     Console.WriteLine($"【{t.AsyncState}】任务已完成  当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                 });
                taskFactory.ContinueWhenAll(taskList.ToArray(),t=>
                {
                    Console.WriteLine($"所有任务已完成,第一个是{t[0].AsyncState}   当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                });
TaskFactory与AsyncState

   如果不用TaskFactory,直接用Task,可以通过做个子类的方式来搞。

11.Task中两种常见的枚举

  11.1 Task生成时的枚举  public Task(Action action, TaskCreationOptions creationOptions);

    11.1.1 AttachedToParent  :建立了父子关系,父任务想要继续执行,必须等待子任务执行完毕

/* task是父task,task1和task2是子task
 * 如果不加AttachedToParent,三个线程的执行没有先后顺序
 * 如果加AttachedToParent,必须等到task1和task2执行完毕后再执行task
 */
Task task = new Task(() =>
{
    Task task1 = new Task(() =>
    {
        Thread.Sleep(100);
        Debug.WriteLine("task1");
    }, TaskCreationOptions.AttachedToParent);

    Task task2 = new Task(() =>
    {
        Thread.Sleep(10);
        Debug.WriteLine("task2");
    }, TaskCreationOptions.AttachedToParent);

    task1.Start();
    task2.Start();
});

task.Start();
task.Wait();  //task.WaitAll(task1,task2);

Debug.WriteLine("我是主线程!!!!");
AttachedToParent示例

    11.1.2 DenyChildAttach: 不让子任务附加到父任务上去,即使子任务加了AttachedToParent ,父任务如果加上DenyChildAttach的话也无济于事,还是各自执行各自的,没有先后顺序

/* task是父任务,task1和task2是子任务
 * 如果不加AttachedToParent,三个线程的执行没有先后顺序
 * 如果加AttachedToParent,必须等到task1和task2执行完毕后再执行task
 * 如果父任务加了DenyChildAttach,还是各自执行各自的
 */
Task task = new Task(() =>
{
    Task task1 = new Task(() =>
    {
        Thread.Sleep(100);
        Debug.WriteLine("task1");
    }, TaskCreationOptions.AttachedToParent);

    Task task2 = new Task(() =>
    {
        Thread.Sleep(10);
        Debug.WriteLine("task2");
    }, TaskCreationOptions.AttachedToParent);

    task1.Start();
    task2.Start();
},TaskCreationOptions.DenyChildAttach);

task.Start();
task.Wait();  //task.WaitAll(task1,task2);

Debug.WriteLine("我是主线程!!!!");
DenyChildAttach示例

    11.1.3 HideScheduler: 子任务默认不使用父类的Task的Scheduler,而是使用默认的

    11.1.4 LongRunning:如果你明知道是长时间运行的任务,建议你使用此选项,建议使用 “Thread” 而不是“threadPool(租赁公司)"

        如果长期租用不还给threadPool,threadPool为了满足市场需求,会新开一些线程,满足当前使用,如果此时租用线程被归还,这会导致ThreadPool的线程过多,销毁和调度都是一个很大的麻烦

        if ((task.Options & TaskCreationOptions.LongRunning) != 0)
        {
            Thread thread = new Thread(s_longRunningThreadWork);
            thread.IsBackground = true;
            thread.Start(task);
        }
        else
        {
            bool forceGlobal = (task.Options & TaskCreationOptions.PreferFairness) != TaskCreationOptions.None;
            ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);
        }
指定LongRunning C#源码

    11.1.5 PreferFairness: 给你的感觉就是一个”queue队列“的感觉,如果指定PreferFairness会将Task放入到ThreadPool的全局队列中,让work thread进行争抢,默认情况会放到task的一个本地队列中

  11.2 任务延续中使用的枚举  public Task ContinueWith(Action<Task> continuationAction, TaskContinuationOptions continuationOptions);  用在ContinuWith

    11.2.1 不使用取消和任务延续枚举,任务依次执行,分别是task1,task2,task3

Task task1 = new Task(() =>
{
    Thread.Sleep(1000);
    Debug.WriteLine("task1 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now);
});

var task2 = task1.ContinueWith(t =>
{
    Debug.WriteLine("task2 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now);
});

var task3 = task2.ContinueWith(t =>
{
    Debug.WriteLine("task3 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now);
});

task1.Start();

/* 输出
task1 tid=10, dt=02/26/2021 10:11:09
task2 tid=11, dt=02/26/2021 10:11:09
task3 tid=10, dt=02/26/2021 10:11:09
*/
不加任何取消和枚举的任务延续

    11.2.2 添加取消,取消task2,未指定任务延续枚举

 添加取消,不加枚举的任务延续

    11.2.3 TaskContinuationOptions.LazyCancellation,需要等待task1执行完成之后再判断source.token的状态   

CancellationTokenSource source = new CancellationTokenSource(); //定义一个任务取消对象
source.Cancel();    //执行任务取消

Task task1 = new Task(() =>
{
    Thread.Sleep(1000);
    Debug.WriteLine("task1 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
});

var task2 = task1.ContinueWith(t =>
{
    Debug.WriteLine("task2 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
},source.Token,TaskContinuationOptions.LazyCancellation,TaskScheduler.Current);    //即使task1被cancel,延续链依旧没断,只是task2不会执行,先执行task1,再执行task3

var task3 = task2.ContinueWith(t =>
{
    Thread.Sleep(100);
    Debug.WriteLine("task3 tid={0}, dt={1}  task2状态:{2}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"),task2.Status);
});

task1.Start();

/* 输出
task1 tid=10, dt=10:36:12.363
task3 tid=11, dt=10:36:12.468  task2状态:Canceled
*/

/* 说明
需要等待task1执行完成之后再判断source.token的状态
这样的话,就形成了一条链: task1 -> task2 -> task3...
*/
LazyCancellation示例

    11.2.4 TaskContinuationOptions.ExecuteSynchronously,这个枚举就是希望执行前面那个task的thread也在执行本延续任务,可以防止线程切换   

Task task1 = new Task(() =>
{
    Thread.Sleep(1000);
    Debug.WriteLine("task1 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
});

var task2 = task1.ContinueWith(t =>
{
    Debug.WriteLine("task2 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
},TaskContinuationOptions.ExecuteSynchronously);    //task2用task1的线程去执行

task1.Start();

/* 输出
 task1 tid=6, dt=10:47:25.189
 task2 tid=6, dt=10:47:25.192
 */
/* 说明
 * task2 也希望使用 task1的线程去执行,这样可以防止线程切换。。。
 */
TaskContinuationOptions.ExecuteSynchronously示例

    11.2.5 TaskContinuationOptions.OnlyOnRanToCompletion 表示延续任务必须在前面task完成状态才能执行

Task task1 = new Task(() =>
{
    Thread.Sleep(1000);
    Debug.WriteLine("task1 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
    throw new Exception("抛出异常");
});

var task2 = task1.ContinueWith(t =>
{
    Debug.WriteLine("task2 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
},TaskContinuationOptions.OnlyOnRanToCompletion);   //仅在task1正常完成时执行task2,如果task1异常了,task2就不会运行

task1.Start();
OnlyOnRanToCompletion示例

    11.2.6 TaskContinuationOptions.NotOnRanToCompletion 表示延续任务必须在前面task非完成状态才能执行,其余类似

NotOnRanToCompletion 延续任务必须在前面task【非完成状态】才能执行
NotOnFaulted 延续任务必须在前面task【非报错状态】才能执行
OnlyOnCanceled 延续任务必须在前面task【已取消状态】才能执行
NotOnCanceled 延续任务必须在前面task【没有取消状态】才能执行
OnlyOnFaulted 延续任务必须在前面task【报错状态】才能执行
OnlyOnRanToCompletion 延续任务必须在前面task【已完成状态】才能执行

12.Task有返回值 

  12.1 Task<TResult>  继承于Task

Task<int> task1 = Task.Factory.StartNew(() =>
  {
      //...
      Thread.Sleep(1000);
    return 1;
  });

Debug.WriteLine(task1.Result);
Task有返回值

  12.2 ContinueWith<TResult>

Task<int> task1 = Task.Factory.StartNew(() =>
{
    //做一些逻辑运算
    return 1;
});

var task2 = task1.ContinueWith<string>(t =>
{
    int num = t.Result;

    var sum = num + 10;

    return sum.ToString();
});

Debug.WriteLine(task2.Result);
ContinueWith有返回值

  12.3 WhenAll<TResult>  WhenAny<TResult>

Task<int> task1 = Task.Factory.StartNew(() =>
{
    //做一些逻辑运算
    return 1;
});

Task<int> task2 = Task.Factory.StartNew(() =>
{
    //做一些逻辑运算
    return 2;
});

Task.WhenAll<int>(new Task<int>[2] { task1, task2 }).ContinueWith<int>(t =>
{
   int sum = 0;
   foreach (var item in t.Result)
   {
       sum += item;
   }
   Debug.WriteLine(sum);
   return sum;
});

/* 输出  3
 * 同时用到了Continue<TResult>和WhenAll<TResult>
 */
同时用到了Continue和WhenAll

  12.4小结

Task.WaitAll(Task[]) 阻塞,等待Task[]数组里的全部线程任务都完成后方可运行到下一步
Task.WaitAny(Task[]) 阻塞,等待Task[]数组里的某一线程任务完成,方可运行到下一步
Task.WhenAll(Task[]).Continue 不阻塞,等待Task[]数组里的全部线程任务都完成后执行回调
Task.WhenAny(Task[]).Continue 不阻塞,等待Task[]数组里的某一线程任务完成后执行回调

12.Thread.Sleep(2000)与Task.Delay(2000)

  Thread.Sleep(2000);//等待2s    Task.Delay(2000);//延迟2s

  12.1Thread.Sleep(2000)

                Console.WriteLine($"这是Task1单击事件Start       当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                Thread.Sleep(2000);//等待
                stopwatch.Stop();
                Console.WriteLine(stopwatch.ElapsedMilliseconds);
                Console.WriteLine($"这是Task1单击事件End      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
Thread.Sleep(2000),阻塞当前线程2秒,继续向下执行

   12.2Task.Delay(2000)直接替换Thread.Sleep(2000)

                Console.WriteLine($"这是Task1单击事件Start       当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                Task.Delay(2000);//延迟
                stopwatch.Stop();
                Console.WriteLine(stopwatch.ElapsedMilliseconds);
                Console.WriteLine($"这是Task1单击事件End      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
Task.Delay(2000),不会起到等待效果

   12.3Task.Delay(2000)的正确用法

    当运行到某一处时,希望两秒后执行一段代码,但又不想阻塞后面的代码,可以用Task.Delay(2000).ContinueWith()

                Console.WriteLine($"这是Task1单击事件Start       当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                Task.Delay(2000).ContinueWith(t =>
                {//Task.Delay()的返回值仍然是Task,  Task.ContinueWith()
                    stopwatch.Stop();
                    Console.WriteLine(stopwatch.ElapsedMilliseconds);
                });
                Console.WriteLine($"这是Task1单击事件End      当前线程:【{Thread.CurrentThread.ManagedThreadId.ToString("00")}】 时间:{DateTime.Now.ToString("HH:mm:ss.fff")}");
Task.Delay(2000).ContinueWith()

 13.多线程中的异常处理

  13.1 主线程的异常捕捉不到子线程的异常信息。所以新开辟的子线程要写属于自己的try...catch...

  子线程如果不阻塞,主线程是捕捉不到。但最最好还是在子线程里书写自己的异常捕捉机制。

            Console.WriteLine($"这是主线程单击事件Start  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
            List<Task> listTask = new List<Task>();
            try
            {
                for (int i = 0; i < 5; i++)
                {
                    Action<string> action = t =>
                      {
                          try
                          {
                              Thread.Sleep(100);
                              if (t.Equals("for2"))
                              {
                                  Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行异常");
                                  throw new Exception($"这是{t},刨出异常");
                              }
                              if (t.Equals("for3"))
                              {
                                  Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行异常");
                                  throw new Exception($"这是{t},刨出异常");
                              }
                              Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行成功");
                          }
                          catch (Exception ex)
                          {
                              Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}     异常信息:{ex.Message}");
                          }
                      };
                    string k = $"for{i}";
                    listTask.Add(Task.Run(() => action.Invoke(k)));
                }
                Task.WaitAll(listTask.ToArray());
            }
            catch (Exception ex)
            {
                Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}     异常信息:{ex.Message}");
            }
            Console.WriteLine($"这是主线程单击事件End  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
多线程异常处理方法

  13.2 AggregateException集合

  当wait、TResult时会抛出异常;可以一个一个遍历异常,Handle遍历异常数组,需要上抛return false,不需要上抛return true

try
{
    try
    {
        task.Wait();
    }
    catch (AggregateException ex)
    {
        foreach (var item in ex.InnerExceptions)
        {
            Console.WriteLine($"{item.InnerException.Message}  {item.GetType().Name}");
        }
        ex.Handle(x =>
        {
            if (x.InnerException.Message == "这是task1异常")
            {
                return true;  //不往上抛
            }
            else
            {
                return false;  //往上抛
            }
        });
    }
}
catch (AggregateException ex)
{
    foreach (var item in ex.InnerExceptions)
    {//输出  这是task2异常
        Console.WriteLine($"{item.InnerException.Message}  {item.GetType().Name}");
    }  
}
AggregateException示例代码

  14.线程取消

  14.1 应用场景是多个线程并发执行,某个失败后,希望通知别的线程,都停下来;Task是外部无法中止的,Thread.Abort不要考虑因为不靠谱(原因是线程是OS的资源,无法掌控啥时候取消),在这里需要线程自己停止自己,需要用到公共的访问变量,所有的线程都会不断的去访问它,当某个线程执行异常时修改此公共变量(当然这中间有一定的延迟)。

  CancellationTokenSource变量去标志任务是否取消,Cancel()方法用于取消,IsCancellationRequested属性作为每个线程执行的条件;其Token在启动Task的时候传入,如果某一时刻执行Cancel()方法,这个任务会自动放弃,并抛出一个异常。

            Console.WriteLine($"这是主线程单击事件Start  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
            List<Task> listTask = new List<Task>();
            CancellationTokenSource cts = new CancellationTokenSource();
            try
            {
                for (int i = 0; i < 5; i++)
                {
                    Action<string> action = t =>
                      {
                          try
                          {
                              Thread.Sleep(100);
                              if (t.Equals("for2"))
                              {
                                  Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行失败");
                                  throw new Exception($"这是{t},刨出异常");
                              }
                              if (t.Equals("for3"))
                              {
                                  Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行失败");
                                  throw new Exception($"这是{t},刨出异常");
                              }
                              if (cts.IsCancellationRequested)
                              {
                                  Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  放弃执行");
                              }
                              else
                              {
                                  Console.WriteLine($"这是{t},当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  执行成功");
                              }
                          }
                          catch (Exception ex)
                          {
                              cts.Cancel();
                              Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}     异常信息:{ex.Message}");
                          }
                      };
                    string k = $"for{i}";
                    listTask.Add(Task.Run(() => action.Invoke(k),cts.Token));
                }
                Task.WaitAll(listTask.ToArray());
            }
            catch (Exception ex)
            {
                Console.WriteLine($"当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}  当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}     异常信息:{ex.Message}");
            }
            Console.WriteLine($"这是主线程单击事件End  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
CancellationTokenSource应用

  14.2 Thread中的取消

var isStop = false; //定义一个公有变量

var thread = new Thread(() =>
{
    while (!isStop)
    {
        Thread.Sleep(100);
        Debug.WriteLine("当前thread={0} 正在运行", Thread.CurrentThread.ManagedThreadId);
    }
});
thread.Start();

Thread.Sleep(1000);
isStop = true;  //多个线程操作公有变量,不用锁的话,存在Bug风险
Thread中的取消,比较low

  14.3 Task中用CancellationTokenSource取消任务

    14.3.1 取消时做一些事情,希望有一个函数能够被触发,这个触发可以做一些资源的清理,又或者是更新数据库信息

CancellationTokenSource cts = new CancellationTokenSource();

cts.Token.Register(() =>
{
    //如果当前的token被取消,此函数将会被执行
    Debug.WriteLine("当前source已经被取消,现在可以做资源清理了。。。。");
});

var task = Task.Factory.StartNew(() =>
  {
      while (!cts.IsCancellationRequested)
      {
          Thread.Sleep(100);
          Debug.WriteLine("当前thread={0} 正在运行", Thread.CurrentThread.ManagedThreadId);
      }
  }, cts.Token);

Thread.Sleep(1000);
cts.Cancel();
cts.Token.Register示例

    14.3.2 延时取消,可以用CancelAfter,也可以在构造函数中指定延时时长

CancellationTokenSource cts = new CancellationTokenSource();

var task = Task.Factory.StartNew(() =>
  {
      while (!cts.IsCancellationRequested)
      {
          Thread.Sleep(100);
          Debug.WriteLine("当前thread={0} 正在运行", Thread.CurrentThread.ManagedThreadId);
      }
  }, cts.Token);

cts.CancelAfter(new TimeSpan(0, 0, 0, 1));  //延时1秒取消
cts.CancelAfter延时1秒取消

    14.3.3 取消组合,是and的逻辑关系

CancellationTokenSource source1 = new CancellationTokenSource();

//现在要让source1取消
source1.Cancel();

CancellationTokenSource source2 = new CancellationTokenSource();

var combineSource = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token);

Debug.WriteLine("s1={0}  s2={1}  s3={2}", source1.IsCancellationRequested,
                                         source2.IsCancellationRequested,
                                         combineSource.IsCancellationRequested);

/* 输出
 s1=True  s2=False  s3=True
 */
/* 说明
 * s3 = s1 && s2
 */
CreateLinkedTokenSource组合

    14.3.4 ThrowIfCancellationRequested

ThrowIfCancellationRequested 比 IsCancellationRequested 多了throw。。。

如果一个任务被取消,我希望代码抛出一个异常。。。

if(IsCancellationRequested) throw new Exception("adasdaf");

== 等价操作 ==

throwIfCancellationRequested();
ThrowIfCancellationRequested解释

 

 

 15.多线程中的临时变量

  15.1直接引用for循环中的i

            Console.WriteLine($"这是主线程单击事件Start  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
            for (int i = 0; i < 5; i++)
            {
                Task.Run(() =>
                {
                    Console.WriteLine($"这是for循环,i={i}");
                });
            }
            Console.WriteLine($"这是主线程单击事件End  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
直接引用for循环中的i

   为什么都等于5,线程并没有阻塞,所以5次循环下来之后i就是5啦。

  15.2直接引用循环外的局部变量

            Console.WriteLine($"这是主线程单击事件Start  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
            int k = 0;
            for (int i = 0; i < 5; i++)
            {
                k = i;
                new Action(() =>
                {
                    Console.WriteLine($"这是for循环,i={k}");
                }).BeginInvoke(null, null);
            }
            Console.WriteLine($"这是主线程单击事件End  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
不用i,直接把k声明在循环外

    为什么都等于4,线程没有阻塞,其实最主要的原因是声明了一次k,全程只有一个k。

  15.3在循环内每次声明赋值

            Console.WriteLine($"这是主线程单击事件Start  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
            for (int i = 0; i < 5; i++)
            {
                int k = i;
                new Action(() =>
                {
                    Console.WriteLine($"这是for循环,i={k}");
                }).BeginInvoke(null, null);
            }
            Console.WriteLine($"这是主线程单击事件End  当前线程{Thread.CurrentThread.ManagedThreadId.ToString("00")}   当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
正确的做法,每次在循环内声明赋值

  16.线程安全

  共有变量(共享资源 )指:都能访问的局部变量、全局变量、数据库中的一个值、磁盘文件等,如果多个线程都去操作一个共有变量,可能会出现意外的结果,这就是多线程并不安全

  16.1多线程并不安全

            List<Task> listTask = new List<Task>();
            int sum = 0;
            for (int i = 0; i < 10000; i++)
            {
                listTask.Add(Task.Run(() =>
                {
                    sum += 1;
                }));
            }
            Task.WhenAll(listTask.ToArray()).ContinueWith((t) =>
            {
                Console.WriteLine($"最终结果:{sum}");
            });
没有lock

可见结果并不是意料之中的10000.

  16.2加lock

        //private:定义为私有,防止外界也去lock    static:全场唯一   readonly:不要随意赋值  object表示引用类型
        private static readonly object objLock = new object();
        private void button1_Click(object sender, EventArgs e)
        {
            List<Task> listTask = new List<Task>();
            int sum = 0;
            for (int i = 0; i < 10000; i++)
            {
                listTask.Add(Task.Run(() =>
                {
                    lock (objLock)
                    {
                        //lock后的方法块,任意时刻只有一个线程可以进入  
                        //只能锁引用类型,相当于占用这个引用链接   
                        //string虽然也是引用类型,但不能用string,因为什么享元模式
                        sum += 1;
                    }
                }));
            }
            Task.WhenAll(listTask.ToArray()).ContinueWith((t) =>
            {
                Console.WriteLine($"最终结果:{sum}");
            });
        }
加了lock锁

可见结果是意料之中的10000

  16.3有关lock的深思

  lock虽然能解决线程安全的问题,同一时刻只能有一个线程可以运行lock后的程序块不并发,但是牺牲了性能,所以有两个建议:

  其一、尽可能得缩小lock的范围;其二、尽量不要定义共有变量,可以通过数据拆分来避免冲突

 17.async与await

  await与async通常成对出现,async放在方法名前(private后面),await放在task对象前。如果只在方法名前加一个async,没有任何意义;如果只在方法内task前面加一个await,会报错。在await task后面的语句,类似于一个回调,方法运行到await task时会自动返回返回主线程继续运行,要等待子线程运行结束后才会继续运行这个回调,并且这个回调的线程是不确定的,可能是主线程,可能是子线程,也可能是其他线程。

  不用用void作为async方法的返回类型,如果没有返回值,也要Task,如果有返回值,要返回Task<T>;仅限于编写事件处理程序需要返回void

   17.1 类似于用一种同步的方法写异步。举例说在主线程中运行了一个task,并且希望在这个子线程task执行之后再运行另一个事件task1,那么就需要task.ContinueWith(task1),在这里task.ContinueWith(task1)就等于await task;task1,其实说白了就是ContinueWith=await

        /* 使用swait/async可以把异步当成同步来使用
         * 这个示例演示一个没有返回值的多线程任务,模拟人一天的动作
         * 其执行结果的先后顺序是【按钮单击开始】【NoReturn方法Start】【开始吃饭】【吃饭结束】【开始工作】【工作结束】【工作结束】【开始睡觉】【NoReturn方法End】【按钮单击结束】
         * 这个示例可能并无实际的意义,但只是用来模拟这种await/async的使用方法
         * 不过请注意使用async的方法虽说没有返回值(并没有具体的return task),但其结果返回的是Task
         */
        private async void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine($"ClickEventStart     【按钮单击开始】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            await NoReturn_AwaitAsync();
            Console.WriteLine($"ClickEventEnd       【按钮单击结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        }

        private async static Task NoReturn_AwaitAsync()
        {
            Console.WriteLine($"【NoReturn方法Start】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            await Task.Run(()=>
            {
                Console.WriteLine($"NoReturn方法await之前   【开始吃饭】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                Thread.Sleep(500);
                Console.WriteLine($"NoReturn方法await之后   【吃饭结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            });
            await Task.Run(() =>
            {
                Console.WriteLine($"NoReturn方法await之前   【开始工作】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                Thread.Sleep(500);
                Console.WriteLine($"NoReturn方法await之后   【工作结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            });
            Console.WriteLine($"NoReturn方法await之后   【开始睡觉】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Console.WriteLine($"【NoReturn方法End】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        }
无返回值的await/async使用

  

   同样的方法从await/async换成Task.ContinueWith来一步步回调则显得代码很冗余

        private static void NoReturn_Task_ContinueWith()
        {
            Console.WriteLine($"【NoReturn方法Start】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Task.Run(() =>
            {
                Console.WriteLine($"NoReturn方法await之前   【开始吃饭】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                Thread.Sleep(500);
                Console.WriteLine($"NoReturn方法await之后   【吃饭结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            }).ContinueWith(t=>
            {
                Task.Run(() =>
                {
                    Console.WriteLine($"NoReturn方法await之前   【开始工作】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    Thread.Sleep(500);
                    Console.WriteLine($"NoReturn方法await之后   【工作结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                }).ContinueWith(t1=>
                {
                    Console.WriteLine($"NoReturn方法await之后   【开始睡觉】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                    Console.WriteLine($"【NoReturn方法End】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                });
            });
        }
使用Task.ContinueWith()代替await/async

  17.2 task有返回值的场景

        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine($"ClickEventStart     【按钮单击开始】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Task<int> task = Task.Run(() => DoSomething("杨三少"));
            int result = task.Result;//这一步子线程会阻塞主线程,因为主线程需要调用子线程的执行结果
            Console.WriteLine($"子线程执行结果为{result}");
            Console.WriteLine($"ClickEventEnd       【按钮单击结束】    当前线程:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        }

        private int DoSomething(string name)
        {
            Console.WriteLine($"Task【{name}】 线程ID:{Thread.CurrentThread.ManagedThreadId} 是否在线程池中:{Thread.CurrentThread.IsThreadPoolThread}");
            return 22;
        }
不实用await/async运行Task返回值的示例

  17.3 这两个关键词适合专用于处理一些文件IO,【潜规则】使用的是ThreadPool、IOStream

  网络IO、文件IO都有一些异步方法  例如MemoryStream、FileStream、WebRequest

  如果返回值是task,都可以使用await进行等待

  而Task 则是最大限度的压榨 workThread

async static Task<string> GetString()
{
    using (FileStream fs = new FileStream(Environment.CurrentDirectory + "//1.txt", FileMode.Open))
    {
        var bytes = new byte[fs.Length];
        var len = await fs.ReadAsync(bytes, 0, bytes.Length);
        var str = Encoding.Default.GetString(bytes, 0, bytes.Length);
        return str;
    }
    return "";
}


//调用
Task<string> info = GetString();
Console.WriteLine(info.Result);   //打印hello world
使用async和await读取本地txt文件

  17.4 使用async和await的优缺点

  优点:代码简洁,把异步的代码形式写成了同步方式; 提到开发效率

  缺点:如果你用同步的思维去理解,容易出问题,返回值对不上;我们在编译器层面看到的代码,不见得是真的代码

  

  17.5 async和await深层剖析

static async Task<string> Hello()
{
    //主线程执行,底层还会调用一个 AwaitUnsafeOnCompleted 委托给线程池
    Console.WriteLine("hello world");

    //在工作线程中执行
    var x = await Task.Run(() =>
      {
          Console.WriteLine("i'm middle");

          return "i'm ok";
      });

    Console.WriteLine("我是结尾哦:{0}", x);
    return x;
}

//调用
var info = Hello().Result;
Console.WriteLine(info);
Console.Read();
async和await应用源码

   

[AsyncStateMachine(typeof(<Hello>d__1))]
[DebuggerStepThrough]
private static Task<string> Hello()
{
    <Hello>d__1 stateMachine = new <Hello>d__1();
    stateMachine.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
    stateMachine.<>1__state = -1;
    stateMachine.<>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}

//调用
string info = Hello().Result;
Console.WriteLine(info);
Console.Read();
ILSpy反编译exe的C#代码
//同等代码,内部新建一个状态机类
public class MyStateMachine : IAsyncStateMachine  //异步方法的状态机接口
{
    public AsyncTaskMethodBuilder<string> t_builder;  //异步方法生成器
    public int state;  //状态 初始-1,执行异步方法,完事后赋0,执行后续操作
    private MyStateMachine machine = null;
    private TaskAwaiter<string> myawaiter;
    string result = string.Empty;

    public MyStateMachine()
    {
    }

    public void MoveNext()
    {
        try
        {
            switch (state)
            {
                case -1:
                    Console.WriteLine("hello world");
                    var waiter = Task.Run(() =>
                    {
                        Console.WriteLine("i'm middle");
                        return "i'm ok";
                    }).GetAwaiter();

                    state = 0;  //设置下一个状态
                    myawaiter = waiter;
                    machine = this;

                    //丢给线程池执行了。。。
                    t_builder.AwaitUnsafeOnCompleted(ref waiter, ref machine);
                    break;

                case 0:
                    var j = myawaiter.GetResult();
                    Console.WriteLine("我是结尾哦:{0}", j);
                    t_builder.SetResult(j);
                    break;
            }
        }
        catch (Exception ex)
        {
            t_builder.SetException(ex);  //设置t_builder的异常
        }
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }
}
同等代码,内部新建一个状态机类
static Task<string> Hello()
{
    MyStateMachine machine = new MyStateMachine();
    machine.t_builder = AsyncTaskMethodBuilder<string>.Create(); //包装器,相当于TaskCompletionSource<string>
    machine.state = -1;
    var t_builder = machine.t_builder;
    t_builder.Start(ref machine);
    return machine.t_builder.Task;  //真正返回的还是Task<string>
}
Hello()中声明了一个状态机类实例

   

  17.6 小结

  AsyncTaskMethodBuilder 扮演了一个TaskcomplationSource一个角色,就是做Task的包装器

  state:扮演者状态机状态的角色

  AwaitUnsafeOnCompleted 这个函数是丢给线程池去执行的,当某一时刻执行结束,会调用Movenext

  异步IO处理的流程 压榨IOthread:work thread: 是应用程序主动使用;IO thread: 是clr反向通知的。。

18.定时器

  18.1 ThreadPool的定时器

Debug.WriteLine("main, datetime={0}", DateTime.Now);
ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), new WaitOrTimerCallback((obj, b) =>
{//为false第一次不会马上执行,为true第一次会马上执行
    //做逻辑判断,判断是否在否以时刻执行。。。
    Debug.WriteLine("obj={0},tid={1}, datetime={2}", obj, Thread.CurrentThread.ManagedThreadId,
                                                             DateTime.Now);
}), "hello world", 1000, false);
ThreadPool定时器

  18.2 Timer

  System.Threading(优先使用这个Timer,最底层的,其他的都是对它的封装)    System.Timers    System.Windows.Forms    System.Web.UI

  18.3 超级强大的Quartz.NET

19. 锁机制    【为什么要用锁,涉及到多线程同时对共享资源的访问和调用安全,同一时刻某一共享变量只能被一个线程使用,否则会共享资源混乱】

  19.1 用户模式锁   【通过一些cpu指令或者一个死循环】  达到thread等待和休眠

    19.1.1 易变结构  volatile

volatile修饰符通常用于由多个线程访问但不实用lock语句对访问进行序列化的字段,促进线程安全,让线程按顺序操作  

//这段代码release版本在我的电脑上并没异常,可能微软修复这个Bug
bool isStop = false;

var t = new Thread(() =>
{
    var isSuccess = false;

    while (!isStop)
    {
        isSuccess = !isSuccess;
    }
});

t.Start();

Thread.Sleep(1000);
isStop = true;
t.Join();

Console.WriteLine("主线程执行结束!");
Console.ReadLine();
示例代码:一个线程读,一个写,在release的某种情况下,会有bug
//volatile关键字:1.不可以底层对代码进行优化;2.我的read和write都是从memrory中读取,读取的都是最新的
private static volatile bool isStop = false;

static void Main(string[] args)
{
    //bool isStop = false;

    var t = new Thread(() =>
    {
        var isSuccess = false;

        while (!isStop)
        {
            isSuccess = !isSuccess;
        }
    });

    t.Start();

    Thread.Sleep(1000);
    isStop = true;
    t.Join();

    Console.WriteLine("主线程执行结束!");
    Console.ReadLine();
}
使用volatile对代码进行优化

    19.1.2 互锁结构  Interlocked  【只能做简单的计算】

int sum = 5;
Interlocked.Increment(ref sum);     //sum=6
Interlocked.Decrement(ref sum);     //sum=5
Interlocked.Add(ref sum, 10);       //sum=15
Interlocked.Exchange(ref sum, 1);   //sum=1
Interlocked.CompareExchange(ref sum, 100, 1);     //第一个参数和带三个参数相等,会把第二个参数赋给第一个参数,否则不变  sum=100
Interlocked.CompareExchange(ref sum, 20, 99);     //第一个参数和带三个参数不相等,第一个参数不变  sum=100
常见的原子操作

    19.1.3 旋转锁   SpinLock   用户模式和内核模式,能把内容留到用户模式的话就不要将其推送到内核模式

private static SpinLock spin = new SpinLock();
static void Main(string[] args)
{
    for (int i = 0; i < 5; i++)
    {
        Task.Factory.StartNew(() =>
        {
            Run();
        });
    }
    Console.Read();
}

static int nums = 0;
static void Run()
{
    try
    {
        for (int i = 0; i < 100; i++)
        {
            var b = false;
            spin.TryEnter(ref b);      //获取锁  不使用锁,顺序会乱
            Console.WriteLine(nums++);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    finally
    {
        spin.Exit();   //退出锁
    }
}
SpinLock 自旋锁机制

  19.2 内核模式锁   【调用win32底层的代码,来实现thread的各种操作】  eg  Thread.Sleep()

  不要轻易使用内核模式锁,比较重代价太大,能用混合锁就不要用内核模式锁

    19.2.1 事件锁【开关锁,使用true、false的变量进行控制】

      自动事件锁:AutoResetEvent       手动事件锁:ManualResetEvent   Set()   Reset()    WaitOne()

    19.2.2 信号量 Semaphore

Semaphore seLock = new Semaphore(1, 10);  //1表示可以同时授予的信号量的初始请求数  10表示可以同时授予的信号量的最大请求数
seLock.WaitOne();  //阻塞当前线程
seLock.Release();   //退出信号量并返回前一个计数
Semaphore
static Semaphore semaphore = new Semaphore(1, 10);
static void Main(string[] args)
{
    semaphore.Release(1);     //初始化指定一个线程,现在又加了一个线程,所以是两个线程一起跑
    for (int i = 0; i < 20; i++)
    {
        Task.Run(() => Run());
    }

    Thread.Sleep(3000);
    semaphore.Release(5);     //继续释放线程个数,允许5+2=7个线程同时跑
    Console.Read();
}

static void Run()
{
    semaphore.WaitOne();
    Thread.Sleep(1000);
    Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId}   当前时间:{DateTime.Now}");
    semaphore.Release();
}
在运行中放开Semaphore的运行线程个数

    

 

 

    19.2.3 互斥锁 mutex

Mutex mutex = new Mutex();
mutex.WaitOne();   //阻塞当前线程
//。。。
mutex.ReleaseMutex();   //释放一次
互斥锁

    19.2.4 读写锁  ReaderWriteLock

//多个线程可以一起读, 只能让要给线程去写
//模拟:多个线程读,一个线程写,那么写的线程是否会阻止读取的线程  答:会
//一般读写 8/2 开   不管写入还是读取时间太久,都会导致另一方超时
static ReaderWriterLock rwlock = new ReaderWriterLock();

static void Main(string[] args)
{
    //比如开启5个task读
    for (int i = 0; i < 5; i++)
    {
        Task.Factory.StartNew(() =>
        {
            Read();
        });
    }

    //一个线程写
    Task.Factory.StartNew(() =>
    {
        Write();
    });

    Console.Read();
}

/// <summary>
/// 线程读
/// </summary>
static void Read()
{
    while (true)
    {
        Thread.Sleep(10);

        rwlock.AcquireReaderLock(int.MaxValue);   //设置读超时

        Thread.Sleep(10);
        Console.WriteLine("当前 t={0} 进行读取 {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now);

        rwlock.ReleaseReaderLock();   //释放锁
    }
}

/// <summary>
/// 线程写
/// </summary>
static void Write()
{
    while (true)
    {
        //3s进行一次写操作
        Thread.Sleep(3000);

        rwlock.AcquireWriterLock(int.MaxValue);   //设置写超时

        Thread.Sleep(3000);
        Console.WriteLine("当前 t={0} 进行写入。。。。。。{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now);

        rwlock.ReleaseWriterLock();   //释放锁
    }
}
ReaderWriterLock 读写锁

     19.2.5 限制线程数  CountdownEvent

//CountdownEvent 限制线程数的一个机制
//搭建一个使用多个线程从一张表中读取数据的场景
//三张表  Orders   Products   Users
//每个表都是用使用多线程去读取  eg Orders(10个线程)   Products(5个线程)   Users(2个线程)
//如果不使用CountdownEvent  使用ContinueWith + TaskCreationOptions.AttachedToParent也是可以的

static CountdownEvent cdeLock = new CountdownEvent(10);  //默认10个线程
static void Main(string[] args)
{
    //加载Orders搞定
    cdeLock.Reset(10); //重置当前的threadcount上限
    for (int i = 0; i < cdeLock.InitialCount; i++)
    {
        Task.Factory.StartNew(() =>
        {
            LoadOrders();
        });
    }
    cdeLock.Wait();   //相当于我们的Task.WaitAll
    Console.WriteLine("所有的Orders都加载完毕。。。。。。。。。。。。。。。。。。。。。");

    //加载Product搞定
    cdeLock.Reset(5);
    for (int i = 0; i < cdeLock.InitialCount; i++)
    {
        Task.Factory.StartNew(() =>
        {
            LoadProducts();
        });
    }
    cdeLock.Wait();
    Console.WriteLine("所有的Products都加载完毕。。。。。。。。。。。。。。。。。。。。。");

    //加载Users搞定
    cdeLock.Reset(2);
    for (int i = 0; i < cdeLock.InitialCount; i++)
    {
        Task.Factory.StartNew(() =>
        {
            LoadUsers();
        });
    }
    cdeLock.Wait();
    Console.WriteLine("所有的Users都加载完毕。。。。。。。。。。。。。。。。。。。。。");

    Console.WriteLine("所有的表数据都执行结束了。。。恭喜恭喜。。。。");
    Console.Read();
}

/// <summary>
/// 10 threads
/// </summary>
static void LoadOrders()
{
    //...业务逻辑
    Console.WriteLine("当前Orders正在加载中。。。{0}", Thread.CurrentThread.ManagedThreadId);
    cdeLock.Signal();    //将当前的threadcount--操作
}

/// <summary>
/// 5 threads
/// </summary>
static void LoadProducts()
{
    //...业务逻辑
    Console.WriteLine("当前Products正在加载中。。。{0}", Thread.CurrentThread.ManagedThreadId);
    cdeLock.Signal();
}

/// <summary>
/// 2 threads
/// </summary>
static void LoadUsers()
{
    //...业务逻辑
    Console.WriteLine("当前Users正在加载中。。。{0}", Thread.CurrentThread.ManagedThreadId);
    cdeLock.Signal();
}
CountdownEvent 限制线程个数

     19.3 混合锁 Slim后缀    用户模式锁+内核模式锁 

    19.3.1 Thread.Sleep(1)  让线程休眠1ms;     Thread.Sleep(0)  让线程放弃当前的时间片,让本线程更高或者同等线程得到时间片运行;   Thread.Yield() 让线程立即放弃当前的时间片,可以让更低级别的线程得到运行,当其他thread时间片用完,本thread再度唤醒       Yield < Sleep(0) < Sleep(1)     一个时间片=30ms

    19.3.2 内核模式锁和混合锁的比较

    混合锁:先在用户模式下内旋,如果超过一定的阈值,会切换到内核锁;在内旋的情况下,我们会看到大量的Sleep(0),Sleep(1),Yield等语法

内核模式锁 混合锁
Semaphore 继承自WaitHandle SemaphoreSlim 继承自IDisposable
ManualResetEvent 继承自EventWaitHandle-->>WaitHandle ManualResetEventSlim 继承自IDisposable
ReaderWriterLock 继承自CriticalFinalizerObject ReaderWriterLockSlim 继承自IDisposable
性能低:调用了win32 API 性能高,并且Wait()方法有多个重载,支持任务取消

    

 

 

   19.4 监视锁 lock和Monitor

    19.4.1 Monitor  限定线程个数的一把锁    Enter锁住某一个资源;Exit退出某一个资源

static object lockMe = new object();    //锁的引用类型
static int nums = 0;
static void Main(string[] args)
{
    for (int i = 0; i < 5; i++)
    {
        Task.Factory.StartNew(() =>
        {
            Run();
        });
    }
    Console.Read();
}

static void Run()
{
    for (int i = 0; i < 100; i++)
    {
        var b = false;
        try   //为了严谨性,当时用Monitor时需要加Try...catch...
        {
            Monitor.Enter(lockMe, ref b);  //b用于指示到底有没有真正获取到锁,如果获取失败,也就不用Exit
            Console.WriteLine(nums++);     //多个线程都对num++,如果不加锁,将是乱序
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            if (b) Monitor.Exit(lockMe);
        }
    }
}
Monitor的常规用法

    19.4.2 lock语法糖    凡是简化我们编程的方式,基本上都叫语法糖;语法糖是编译器层面的,底层的IL,还是使用Monitor  

    因为众多的锁机制中,唯独只有Monitor有专用的语法糖,所以说非常受重视,本质就是利用堆上的同步块实现资源锁定

static void Run()
{
    for (int i = 0; i < 100; i++)
    {
        lock (lockMe)
        {
            Console.WriteLine(nums++);     //多个线程都对num++,如果不加锁,将是乱序
        }
    }
使用lock简单代码可实现与Monitor一样的功能
.method private hidebysig static 
    void Run () cil managed 
{
    // Method begins at RVA 0x20a0
    // Code size 72 (0x48)
    .maxstack 3
    .locals init (
        [0] int32 i,
        [1] object,
        [2] bool,
        [3] bool
    )

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    IL_0003: br.s IL_003e
    // loop start (head: IL_003e)
        IL_0005: nop
        IL_0006: ldsfld object _021_lock和Monitor.Program::lockMe
        IL_000b: stloc.1
        IL_000c: ldc.i4.0
        IL_000d: stloc.2
        .try
        {
            IL_000e: ldloc.1
            IL_000f: ldloca.s 2
            IL_0011: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)
            IL_0016: nop
            IL_0017: nop
            IL_0018: ldsfld int32 _021_lock和Monitor.Program::nums
            IL_001d: dup
            IL_001e: ldc.i4.1
            IL_001f: add
            IL_0020: stsfld int32 _021_lock和Monitor.Program::nums
            IL_0025: call void [mscorlib]System.Console::WriteLine(int32)
            IL_002a: nop
            IL_002b: nop
            IL_002c: leave.s IL_0039
        } // end .try
        finally
        {
            IL_002e: ldloc.2
            IL_002f: brfalse.s IL_0038

            IL_0031: ldloc.1
            IL_0032: call void [mscorlib]System.Threading.Monitor::Exit(object)
            IL_0037: nop

            IL_0038: endfinally
        } // end handler

        IL_0039: nop
        IL_003a: ldloc.0
        IL_003b: ldc.i4.1
        IL_003c: add
        IL_003d: stloc.0

        IL_003e: ldloc.0
        IL_003f: ldc.i4.s 100
        IL_0041: clt
        IL_0043: stloc.3
        IL_0044: ldloc.3
        IL_0045: brtrue.s IL_0005
    // end loop

    IL_0047: ret
} // end of method Program::Run
IL层面的lock其实使用的还是Monitor

    19.4.3 小结

    Enter中添加的对象,相当于把对象的同步块索引和CLR的同步块数组进行关联

    Exit中释放的资源,相当于把对象的同步快索引和CLR的同步块数组进行了解绑

    要注意:锁住的引用类型一定是可访问的线程必须能够访问到的;锁住的资源千万不要使用值类型,因为值类型是拷贝的;锁住的资源的作用域必须足够大

    

20.Parallel并行处理

  并行时,可指定当前有几个线程参与计算;不让所有的thread参与计算,不让cpu跑的太凶猛了

  最后会使用ParallelForReplicatingTask 进行处理;

  不要在Parallel.For中使用break或者stop,或许会给你引入一些不必要的bug;因为大家都是并行执行的,所以别的线程是刹不住车的

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 100; i++)
{
    Console.WriteLine("串行:" + i);
}
stopwatch.Stop();
Console.WriteLine("串行耗时:" + stopwatch.ElapsedMilliseconds);  //20ms
stopwatch.Restart();
ConcurrentStack<int> stack = new ConcurrentStack<int>();  //线程安全的栈
Parallel.For(0, 100, (item) =>
  {
      Console.WriteLine("并:" + item);
      stack.Push(item);
  });
stopwatch.Stop();
Console.WriteLine("并行耗时:" + stopwatch.ElapsedMilliseconds + "\r\n" + string.Join(",", stack));  //耗时180ms
Parallel的For
//使用parallel.For计算1到99的和
var totalNums = 0;
//类似聚合函数
Parallel.For<int>(1, 100, 
() => { return 0; }, 
(current, loop, total) =>
{
    total += (int)current;

    return total;
}, 
(total) =>
{
    Interlocked.Add(ref totalNums, total);
});
Console.WriteLine(totalNums);
使用Parallel计算1-99

Dictionary<int, int> dic = new Dictionary<int, int>()
{
    {1,100},
    {2,200 },
    {3,300 }
};
Parallel.ForEach(dic, (item) =>
{
    Console.WriteLine(item.Key);  //1  2  3
});

Parallel.Invoke(() =>
{
    Console.WriteLine("我是并行计算1 " + Thread.CurrentThread.ManagedThreadId);  //Invoke中的是不同的线程
}, () =>
{
    Console.WriteLine("我是并行计算2 " + Thread.CurrentThread.ManagedThreadId);
});
Parallel的Foreach和Invoke

  Parallel的For处理的是数组,Foreach处理的是集合运算(非数组)。Parallel核心就是分区,每个线程处理一个区域 。

        //
        // 摘要:
        //     执行具有 64 位索引和线程本地数据的 for(在 Visual Basic 中为 For)循环,其中可能会并行运行迭代,而且可以监视和操作循环的状态。
        //
        // 参数:
        //   fromInclusive:
        //     开始索引(含)。
        //
        //   toExclusive:
        //     结束索引(不含)。
        //
        //   localInit:
        //     用于返回每个任务的本地数据的初始状态的函数委托。
        //
        //   body:
        //     将为每个迭代调用一次的委托。
        //
        //   localFinally:
        //     用于对每个任务的本地状态执行一个最终操作的委托。
        //
        // 类型参数:
        //   TLocal:
        //     线程本地数据的类型。
        //
        // 返回结果:
        //     包含有关已完成的循环部分的信息的结构。
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     body 参数为 null。 或 - localInit 参数为 null。 或 - localFinally 参数为 null。
        //
        //   T:System.AggregateException:
        //     包含在所有线程上引发的全部单个异常的异常。
        public static ParallelLoopResult For<TLocal>(long fromInclusive, long toExclusive, Func<TLocal> localInit, Func<long, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally);
Parallel.For 讲解

21.PLinq

  底层基于Task的一些编程模型,让我们快速进行并行计算;最灵活的东西莫过于自己去写业务逻辑。。封装的越厉害,灵活性越差,性能自然也越差

  WithDegreeOfParallelism(Environment.ProcessorCount-1) 

//普通的linq
var nums = Enumerable.Range(0, 100);
var query = from n in nums
            select new
            {
                thread = Thread.CurrentThread.ManagedThreadId,
                num = n
            };
foreach (var item in query)
{
    Console.WriteLine(item);
}
普通的linq

  

AsParallel() 可将串行的代码转换成并行
AsOrdered() 将并行结果还是按照 未排序的样子进行排序
WithDegreeOfParallelism (Environment.ProcessorCount-1) 
WithCancellation  如果执行之前被取消,那就不要执行了

WithExecutionMode

此参数可以告诉系统当前是否强制并行

  21.1 AsParallel()

//并行查询AsParallel()
var nums = Enumerable.Range(0, 100);
var query = from n in nums.AsParallel()
            select new
            {
                thread = Thread.CurrentThread.ManagedThreadId,
                num = n
            };
foreach (var item in query)
{
    Console.WriteLine(item);
}
并行查询AsParallel()

  21.2 AsOrdered()

[10,1,2,3,4] => 并行计算.AsOrdered => [10,1,2,3,4]

[10,1,2,3,4] => orderby => [1,2,3,4,10]

22.TaskSchedule Task调度器

  Task任务的执行都需要经过Schedule,Task的核心是Schedule调度器,Schedule需要把任务安排到线程或者线程池中

  Task调度器有两种,默认是ThreadPoolTaskSchedule线程池,还有一个是SynchronizationContextTaskSchedule同步上下文调度器

  22.1 ThreadPoolTaskSchedule  默认Task的调度形式

//ThreadPoolTaskSchedule核心源码

protected internal override void QueueTask(Task task)
    {
                //如果创建方式LongRunning,就以Thread的方式执行,否则以ThreadPool的方式执行
        if ((task.Options & TaskCreationOptions.LongRunning) != 0)
        {
            Thread thread = new Thread(s_longRunningThreadWork);
            thread.IsBackground = true;
            thread.Start(task);
        }
        else
        {
            bool forceGlobal = (task.Options & TaskCreationOptions.PreferFairness) != 0;
            ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);
        }
    }    
ThreadPoolTaskSchedule核心源码

  22.2 SynchronizationContextTaskScheduler 同步上下文Task调度器

  在winform、wpf中,如果给一个控件赋值,都是在调用invoke方法

  下属例子讲解如何在winform中使用同步上下文任务调度器在工作线程中更新控件

  

    

   

  22.3 自定义TaskSchedule

//自定义TaskSchedule让其默认用Thread方式执行

public class PerThreadTaskSchedule : TaskScheduler
{
    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return Enumerable.Empty<Task>();
    }

    protected override void QueueTask(Task task)
    {
        var thread = new Thread(() =>
         {
             TryExecuteTask(task);
         });
        thread.Start();
    }

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return true;
    }
}
自定义TaskSchedule让其默认用Thread方式执行

 23.多线程模型:同步编程模型SPM   异步编程模型APM   基于事情的编程模型EAP    基于Task的编程模型TAP

  微软大力推广Task,APM和EAP都能包装成Task使用,微软想用Task来统治异步编程领域,解决大一统的问题

  23.1 异步编程模型APM    xxxbegin   xxxend    这两个配对的经典方法,委托给线程池执行

    例如FileStream (ReadBegin和ReadEnd) 配对方法    以及    Action委托都可以异步执行

{
    //使用Task包装AMP
    using (FileStream fs = new FileStream(Environment.CurrentDirectory + "//1.txt", FileMode.Open))
    {
        var bytes = new byte[fs.Length];
        var task = Task.Factory.FromAsync(fs.BeginRead, fs.EndRead, bytes, 0, bytes.Length, string.Empty);
        var nums = task.Result;
        Console.WriteLine(nums);
    }
}

{
    //不使用Task包装AMP
    using (FileStream fs = new FileStream(Environment.CurrentDirectory + "//1.txt", FileMode.Open))
    {
        var bytes = new byte[fs.Length];
        fs.BeginRead(bytes, 0, bytes.Length, (aysc) =>
        {
            var nums = fs.EndRead(aysc);
            Console.WriteLine(nums);
        }, string.Empty);
    }
}


//使用Task包装的两个好处:代码量比较小、使用task更方便,更强大
使用task包装APM xxxbegin xxxend

  23.2 基于事件的编程模型(EAP)  xxxAsync事件模型,eg WebClient

//使用task包装EAP
public static Task<byte[]> GetTaskAsync(string url)
{
    //TaskCompletionSource  Task包装器
    TaskCompletionSource<byte[]> source = new TaskCompletionSource<byte[]>();

    WebClient client = new WebClient();
    //WebClient.DownloadDataCompleted事件
    client.DownloadDataCompleted += (sender, e) =>
    {
        try
        {
            //如果下载完成了,将当前的byte[]给task包装器
            source.TrySetResult(e.Result);
        }
        catch (Exception ex)
        {
            source.TrySetException(ex);
        }
    };
    client.DownloadDataAsync(new Uri(url));
    return source.Task;
}


//调用
var task = GetTaskAsync("http://www.baidu.com");
byte[] data = task.Result;
//使用task包装EAP

 24. 四大并发集合类       Concurrent :同时发生的

   24.1 ConcurrentBag<T>    利用线程槽来分摊Bag中的所有数据;所有数据都是防止在多个插入线程的槽位中。。每个线程一个子集

ThreadLocal 是什么意思??? 每个线程有一个自己的备份(线程不可见)


1. 每一个线程分配一个“链表” 这个链表可以任务是list(ThreadLocalList)


当你Add操作的时候,locals里面有一份新增的数据,【只有本线程看得见】

同时head和next也是有数据的。。。。为什么有??因为我们的算法有一个“偷盗”
的行为。。。

TryTake: 获取数据

如果有三个线程做Add操作,那么三个线程的数据槽中都有一份子集数据。。。

t1: 1,2,3    locals
t2: 1,3,2    locals
t3: 2,3,4    locals  

这个时候,如果你在t3线程中执行了三个TryTake。。

t1: 1,2,3    locals
t2: 1,3,2    locals
t3: empty    locals 

如果这个时候我在t3线程上进行tryTake,怎么办???

这个时候就到Bag的下一级的ttl head 和 next中去找。。。。【steal 偷盗的时候使用的】

    for (threadLocalList = this.m_headList; threadLocalList != null; threadLocalList = threadLocalList.m_nextList)
        {
            list.Add(threadLocalList.m_version);
            if (threadLocalList.m_head != null && this.TrySteal(threadLocalList, out result, take))
            {
                return true;
            }
        }

总结:ConcurrentBag 就是利用线程槽来分摊Bag中的所有数据。

      ConcurrentBag的所有数据都是防止在多个插入线程的槽位中。。每个线程一个子集。。。

链表的头插法。


        static void Main(string[] args)
        {
            ConcurrentBag<int> bag = new ConcurrentBag<int>();

            bag.Add(1);

            bag.Add(2);

            var result = 0;
            bag.TryTake(out result);
        }
ConcurrentBag

  24.2 ConcurrentStack<T>     ConcurrentQueue<T>       ConcurrentDictionary<TKey, TValue>

同步版本 线程安全版本
Stack   数组 ConcurrentStack    链表,使用Interlocked来实现,而没有使用内核锁
Queue   数组 ConcurrentQueue    链表  
Dictionary ConcurrentDictionary    TryAdd   TryGetValue    TryRemove

  如果不想用Concurrent线程安全版本,可以用 同步+lock/其他锁机制

25. 三大救火Bug:CPU过高、死锁、内存爆满

  25.1 CPU过高   死循环,肯定是否一个线程得不到休眠长时间运行容易导致CPU过高

static void Main(string[] args)
{
    Run();
    Console.Read();
}

static void Run()
{
    var task = Task.Factory.StartNew(() =>
    {
        var i = true;
        //这个地方是一个非常复杂的逻辑。导致死循环
        while (true)
        {
            i = !i;
        }
    });
}
导致CPU过高的Bug代码

  步骤:1.生成release x64   2.在“任务管理器”中生成一个dump文件    3.需要用x64 的windbg

   25.2 死锁

private static object lockMe = new object();

static void Main(string[] args)
{
    Run();
    Console.Read();
}

static void Run()
{
    lock (lockMe)    //第一次锁
    {
        var task = Task.Run(() =>
        {
            Console.WriteLine("----- start ---- ");
            Thread.Sleep(1000);
            Run2();
            Console.WriteLine("------ end  -----");
        });

        task.Wait();
    }
}

static void Run2()
{
    lock (lockMe)   //死锁   第二次锁
    {
        Console.WriteLine("我是 run2.。。。。");
    }
}
滥用lock,锁两次第二次一直在等导致死锁

  1. ~*e!clrstack 查看所有线程的堆栈;2. !threads 查看当前的托管线程;3. !syncblk 当前哪一个线程持有锁

  25.3 内存爆满

static StringBuilder sb = new StringBuilder();  //用于存放内存的对象
static void Main(string[] args)
{
    for (int i = 0; i < 10000000; i++)
    {
        sb.Append("hello world");   
    }
    Console.WriteLine("执行完毕");
    Console.Read();
}
分析内存的代码

   !dumpheap -stat   查看clr的托管堆中的各个类型的占用情况

    

  !DumpHeap /d -mt 00007ffb445467d0   //查看当前的方法表  这个方法表很长

  

    

    !DumpObj /d 00000152aed26be8        //查看当前char[]的内容

    

    !gcroot 00000152aed81208     //查看当前地址的根root

   

    

 

 

 

 

 

  

推荐阅读