C#多线程

前期知识:

1.进程和线程是啥?

进程:进程就是一个应用程序,对电脑的各种资源的占用

线程:线程是程序执行的最小单位,任何操作都是线程完成的,线程依托进程存在的,一个进程可以有多个线程

2.多线程
为啥会出现多此线程?

计算机的角度,因为CPU太快了,其他硬件跟不上CPU的速度。CPU可以分为时间片,大概就是时间分片---上下文切换(加载环境--计算--保存环境)。

从微观角度上说,一个核一个时刻,只能执行一个线程;宏观上来说是多线程并发。

另外CPU多核,可以独立工作。例如计算机是4核8线程中,核指的就是物理的核,线程指的是物理的核。

3.C#语言的线程

就是指Thread(.net 1.0的时候就出现了),Thread是一个类,是C#语言多线程对象的封装。

4.同步和异步
这个是指方法的描述。

同步:当前行,当前方法执行完成后,再开始下一行,阻塞式的。

异步:不会等待方法的完成,会直接进入下一行,非阻塞式的。

异步多线程方法发块,因为每个线程并发运算;但是并不是线性增长。同步方法,时间长,资源占用少;异步方法时间快,CPU资源占用多,异步是资源换时间。多线程可能资源不够,还有管理成本。

5.C#中的异步和多线程的区别?

多线程就是多个Thread并发;

异步是硬件式的异步;

6.应用场景
本身异步多线程就是顺序不可控的,当要控制顺序的时候,需要用到异步回调,3种方式可以实现

注意:不要忘记本心,应用多线程的前提:多个独立的任务可以同时运行。

7.委托的异步调用

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace _303Thread
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //不带返回值的委托异步调用
            {
                Action action;
                action = DoSomething;
                //action.Invoke();//同步
                IAsyncResult iAsyncResult = null;
                AsyncCallback async = new AsyncCallback(ia =>
                {
                    Console.WriteLine(object.ReferenceEquals(ia, iAsyncResult));
                    Console.WriteLine("所有任务都完成了");
                });
                iAsyncResult = action.BeginInvoke(async, "button1_Click");
                //上面一行代码的含义:action.BeginInvoke开启一个异步线程,
                //异步线程的执行完,产生一个IAsyncResult
                //最后执行回调委托,async。
            }

            //带返回值的委托异步调用
            {
                Func<int> func = () =>
                {
                    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(100);
                    return DateTime.Now.Day;
                };
                //int result = func.Invoke();

                IAsyncResult asyncResult = func.BeginInvoke(ia =>
                {
                    Console.WriteLine(ia.AsyncState);
                    Console.WriteLine(func.EndInvoke(ia));
                }, "传入的参数");
                //Console.WriteLine($"{func.EndInvoke(asyncResult)}");
            }
        }
        private void DoSomething()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine($"*********DoSomething Start:{Thread.CurrentThread.ManagedThreadId}***********");
                for (int j = 0; j < 100; j++)
                {
                    Thread.Sleep(5);
                }
                Console.WriteLine($"*********DoSomething End:{Thread.CurrentThread.ManagedThreadId}***********");
            }
        }
    }
}

 Task专辑

.net framwork 3.0出现的。命名空间是System.Thread.Tasks。

Task是基于ThreadPool封装的,之后的多线程主流都使用Task。

什么时候用多线程?

首先,任务得能够并发运行。任务可以分拆,且各不相干。

 

 1 using System;
 2 using System.Collections.Concurrent;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading;
 7 using System.Threading.Tasks;
 8 
 9 namespace _00_测试的例子
10 {
11     class Program
12     {
13         static void Main(string[] args)
14         {
15             List<Task> list = new List<Task>();
16             Console.WriteLine($"项目经理建立项目,线程ID:{Thread.CurrentThread.ManagedThreadId}");
17             list.Add(Task.Factory.StartNew(() => DoSomething("小明", "Client")));
18             list.Add(Task.Factory.StartNew(() => DoSomething("小涨", "Portal")));
19             list.Add(Task.Factory.StartNew(() => DoSomething("小王", "Service")));
20 
21             //Task的一个API的含义
22             //Task.WaitAll(list.ToArray());//阻塞当前线程,等待所有任务完成后,才进入下一行,卡界面
23             //Task.WaitAll(list.ToArray(), 1000);//限时等待1000ms,最多就等1000ms
24             //Task.WaitAny(list.ToArray());//阻塞当前线程,等待某个任务完成后,进入下一行,卡界面
25 
26             Task.WhenAny(list.ToArray()).ContinueWith(t =>
27             {
28                 Console.WriteLine($"得意的笑,线程ID:{Thread.CurrentThread.ManagedThreadId}");
29             });//等待某个任务完成后,不卡界面,非阻塞
30             Task.WhenAll(list.ToArray()).ContinueWith(t =>
31             {
32                 Console.WriteLine($"部署环境,联调测试,线程ID:{Thread.CurrentThread.ManagedThreadId}");
33             });//等待所有任务完成后,不卡界面,非阻塞
34 
35             //Console.WriteLine($"告诉甲方,项目可以验收了, 线程ID:{Thread.CurrentThread.ManagedThreadId}");
36             Console.ReadKey();
37         }
38         /// <summary>
39         /// 做事情
40         /// </summary>
41         /// <param name="str1"></param>
42         /// <param name="str2"></param>
43         public static void DoSomething(string str1, string str2)
44         {
45             Console.WriteLine($"我是{str1},Start:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }");
46             long result = 0;
47             for (int i = 0; i < 100000000; i++)
48             {
49                 result += i;
50             }
51             Console.WriteLine($"我是{str1},End:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }");
52         }
53 
54     }
55 }

 Task的一些API

多线程中,Task的4个API熟练使用,就能应付工作中大部分多线程了。

Task.WaitAny:阻塞当前线程,等待某个任务完成,进入下一行,会卡界面
Task.WaitAll:阻塞当前线程,等待所有任务完成,进入下一行,会卡界面
Task.WhenAny:等待某个任务完成后,不卡界面,非阻塞
 Task.WhenAll:等待所有任务完成后,不卡界面,非阻塞
taskFactory.ContinueWhenAny和taskFactory.ContinueWhenAll和上面Task的API功能相同。
Task.WhenAny(list.ToArray()).ContinueWith()和Task.WhenAll(list.ToArray()).ContinueWith()和上面的taskFactory的ContinueWhenAny()和ContinueWhenAll()功能是一样的。
 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Data;
 5 using System.Drawing;
 6 using System.Linq;
 7 using System.Text;
 8 using System.Threading;
 9 using System.Threading.Tasks;
10 using System.Windows.Forms;
11 
12 namespace _00_测试的例子
13 {
14     public partial class Form1 : Form
15     {
16         public Form1()
17         {
18             InitializeComponent();
19         }
20 
21         private void button1_Click(object sender, EventArgs e)
22         {
23             List<Task> list = new List<Task>();
24             Console.WriteLine($"项目经理建立项目,线程ID:{Thread.CurrentThread.ManagedThreadId}");
25             list.Add(Task.Factory.StartNew(() => DoSomething("小明", "Client")));
26             list.Add(Task.Factory.StartNew(() => DoSomething("小涨", "Portal")));
27             list.Add(Task.Factory.StartNew(() => DoSomething("小王", "Service")));
28 
29             //Task的一个API的含义
30             //Task.WaitAny(list.ToArray());//阻塞当前线程,等待某个任务完成后,进入下一行,卡界面
31             //Task.WaitAll(list.ToArray());//阻塞当前线程,等待所有任务完成后,才进入下一行,卡界面
32             //Task.WaitAll(list.ToArray(), 1000);//限时等待1000ms,最多就等1000ms
33 
34             Task.WhenAny(list.ToArray()).ContinueWith(t =>
35                    {
36                        Console.WriteLine($"得意的笑,线程ID:{Thread.CurrentThread.ManagedThreadId}");
37                    });//等待某个任务完成后,不卡界面,非阻塞
38             Task.WhenAll(list.ToArray()).ContinueWith(t =>
39               {
40                   Console.WriteLine($"部署环境,联调测试,线程ID:{Thread.CurrentThread.ManagedThreadId}");
41               });//等待所有任务完成后,不卡界面,非阻塞
42 
43             {//TaskFactory的两个API函数,跟上面Task的函数功能相同
44                 //TaskFactory taskFactory = new TaskFactory();
45                 //taskFactory.ContinueWhenAny(list.ToArray(), t =>
46                 //{
47                 //    Console.WriteLine($"得意的笑,线程ID:{Thread.CurrentThread.ManagedThreadId}");
48                 //});
49 
50                 //taskFactory.ContinueWhenAll(list.ToArray(), tList =>
51                 //{
52                 //    Console.WriteLine($"部署环境,联调测试,线程ID:{Thread.CurrentThread.ManagedThreadId}");
53                 //});
54             }
55 
56             Console.WriteLine($"告诉甲方,项目可以验收了, 线程ID:{Thread.CurrentThread.ManagedThreadId}");
57         }
58         /// <summary>
59         /// 做事情
60         /// </summary>
61         /// <param name="str1"></param>
62         /// <param name="str2"></param>
63         public static void DoSomething(string str1, string str2)
64         {
65             Console.WriteLine($"我是{str1},Start:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }");
66             long result = 0;
67             for (int i = 0; i < 1000000000; i++)
68             {
69                 result += i;
70             }
71             Console.WriteLine($"我是{str1},End:{str2},线程ID是:{Thread.CurrentThread.ManagedThreadId }");
72         }
73     }
74 }

 

await和asycn,.net framwork4.5出现的语法糖

1>通常是成对出现的,async是放到方法上的,任意个方法都可以使用。await是放到Task前面的。

2>await和asycn要么不用,要么用到底。用到最后一层,一层层传到上层。

3>任何一个方法前面都可以加上async,方法里面await可有,也可没有。await一定是出现在task之前

 之所以用,目的是想用同步的方式,写异步操作。

下面一段代码,其中方法Show()是实现方式就是ShowNew()。await后面的那份部分代码,其实就是task.ContinueWith回调函数。

注意:非UI线程中,await后面的动作都是子线程完成的;UI线程中,await后面的动作都是主线程完成的。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 
 8 namespace _00_测试的例子
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             Console.WriteLine($"Main1:{Thread.CurrentThread.ManagedThreadId}");
15             Task task = Show();
16             Console.WriteLine($"Main2:{Thread.CurrentThread.ManagedThreadId}");
17             Console.WriteLine("=====================================================");
18             Console.WriteLine($"Main1==:{Thread.CurrentThread.ManagedThreadId}");
19             Task task2 = ShowNew();
20             Console.WriteLine($"Main2==:{Thread.CurrentThread.ManagedThreadId}");
21             Console.ReadKey();
22         }
23         public async static Task Show()
24         {
25             Console.WriteLine($"Show1:{Thread.CurrentThread.ManagedThreadId}");
26             Task task = Task.Factory.StartNew(() =>
27             {
28 
29                 Console.WriteLine($"Task1:{Thread.CurrentThread.ManagedThreadId}");
30                 Thread.Sleep(1000);
31                 Console.WriteLine($"Task2:{Thread.CurrentThread.ManagedThreadId}");
32             });
33             await task;
34             Console.WriteLine($"Show2:{Thread.CurrentThread.ManagedThreadId}");
35 
36             Task task1 = Task.Factory.StartNew(() =>
37             {
38                 Console.WriteLine($"Task3:{Thread.CurrentThread.ManagedThreadId}");
39                 Thread.Sleep(2000);
40                 Console.WriteLine($"Task4:{Thread.CurrentThread.ManagedThreadId}");
41             });
42             await task1;
43             Console.WriteLine($"Show5:{Thread.CurrentThread.ManagedThreadId}");
44         }
45         public async static Task ShowNew()
46         {
47             Console.WriteLine($"Show1:{Thread.CurrentThread.ManagedThreadId}");
48             Task task = Task.Factory.StartNew(() =>
49             {
50 
51                 Console.WriteLine($"Task1:{Thread.CurrentThread.ManagedThreadId}");
52                 Thread.Sleep(1000);
53                 Console.WriteLine($"Task2:{Thread.CurrentThread.ManagedThreadId}");
54             });
55 
56             Task task4 = task.ContinueWith((t) =>
57               {
58                   Console.WriteLine($"Show2:{Thread.CurrentThread.ManagedThreadId}");
59 
60                   Task task1 = Task.Factory.StartNew(() =>
61                   {
62                       Console.WriteLine($"Task3:{Thread.CurrentThread.ManagedThreadId}");
63                       Thread.Sleep(2000);
64                       Console.WriteLine($"Task4:{Thread.CurrentThread.ManagedThreadId}");
65                   });
66                   task1.ContinueWith(t1 =>
67                   {
68                       Console.WriteLine($"Show5:{Thread.CurrentThread.ManagedThreadId}");
69                   });
70               });
71         }
72     }
73 }

异常处理,线程取消,多线程临时变量

 1>异常处理

子线程里面不允许出现异常,自己处理好异常

2>线程取消

使用场景,多线程并发,某个失败后,通知其他线程,都停了吧别做了。

Task是外部无法终止的,因为线程是OS的资源,无法掌控什么时候取消。线程自身停止自己,访问一个公共变量,修改它,线程不断访问它。

CancellationTokenSource去标志是否取消任务,Cancel取消,IsCancellationRequested是否已经取消。

Token启动Task的时候传入,如果Cancel了,这个任务放弃启动,抛出一个异常。

3>多线程临时变量

下图中,i为什么等于5,k反而是0到4?

 

答案:i最后执行了++,就变成了5。全程只有一个i值。k的话,for循环5次,就有5个k。

异步和多线程的区别?

异步:是对方法的描述,是一个目的或者说是目标。异步不会等待方法完成,会直接进入下一行,非阻塞。

多线程:是多个Thread并发。是一种实现异步的方式。

线程安全

 公共变量:都能访问的局部变量

lock只能锁引用类型,占用这个引用链接

安全队列ConcurrentQueue一个线程完成操作

 1 using System;
 2 using System.Collections.Concurrent;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading;
 7 using System.Threading.Tasks;
 8 
 9 namespace _00_测试的例子
10 {
11     class Program
12     {
13         static int iTotalCount = 0;//全局变量
14         static List<int> list = new List<int>();
15         private static readonly object oValue = new object();
16         static void Main(string[] args)
17         {
18             Start0();
19             Console.ReadKey();
20         }
21         public static void Start0()
22         {
23             TaskFactory taskFactory = new TaskFactory();
24             List<Task> listTask = new List<Task>();
25             int iTotal = 0;
26             for (int i = 0; i < 10000; i++)
27             {
28                 int iValue = i;
29                 listTask.Add(taskFactory.StartNew(() =>
30                 {
31                     //lock(oValue)
32                     //{
33                     iTotalCount += 1;
34                     iTotal += 1;
35                     list.Add(iValue);
36                     //}
37                 }));
38             }
39             Task.WaitAll(listTask.ToArray());
40             Console.WriteLine($"{iTotalCount}");
41             Console.WriteLine($"{iTotal}");
42             Console.WriteLine($"{list.Count}");
43             Console.ReadKey();
44         }
45     }
46 }

上段代码执行结果如下图:循环10000次,实际执行结果却不是10000.因为多个线程同时访问变量,造成了数据丢失

 

 

开放上段代码中屏蔽的部分,如下图,

iTotalCount 和iTotal 和list个数都是10000万次了。这个是Lock锁住了这三个变量,让同一时间,是有一个线程访问。Lock只能锁引用类型,锁的是引用链接。

 

 

posted @ 2022-06-16 18:50  东方承丘  阅读(1309)  评论(0编辑  收藏  举报