.net基础—多线程(二)

Thread

在.NET中最早提供的控制线程类型的类型:System.Threading.Thread类。使用该类型可以直观地创建、控制和结束线程。下面是一个简单的多线程程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void Main(string[] args)
        {
            Console.WriteLine("进入多线程模式:");
 
            for (int i = 0; i < 5; i++)
            {
                Thread t = new Thread(Work);
                t.Start();//开启新线程
            }
 
            Console.ReadKey();
        }
 
        static void Work()
        {
            Console.WriteLine($"开始工作的线程ID:{Thread.CurrentThread.ManagedThreadId} ");
            Thread.Sleep(1000);//假如处理任务耗时1s
            Console.WriteLine($"结束工作的线程ID:{Thread.CurrentThread.ManagedThreadId} ");
        }

 该段代码是在主线程中创建5个线程,每个线程进行独立的工作互不干涉。代码执行结果如下:

 

 Thread中提供了很多控制线程的方法,例如:终止本线程(Abort)、暂停(Suspend)、恢复(Resume)、阻塞调用线程(Join)等等。

ThreadPool

 Thread是.NET Framework 1.0中推出实现多线程的方案,它是直接从计算机系统里抓取的线程,这就存在很多问题,例如:频繁的创建和销毁线程、无节制的创建线程等。所以微软在.NET Framework 2.0中推出ThreadPool(线程池),它是给开发人员提供一个线程容器,在使用多线程的时从容器中抓取线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("进入多线程模式:");
 
            for (int i = 0; i < 10; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(Work));               
            }
 
            Console.ReadKey();
        }
 
        static void Work(object stateInfo)
        {
            Console.WriteLine($"开始工作的线程ID:{Thread.CurrentThread.ManagedThreadId} ");
            //
            Console.WriteLine($"结束工作的线程ID:{Thread.CurrentThread.ManagedThreadId} ");
        }
 
    }

 上面这段就是使用ThreadPool实现多线程,代码执行结果如下:

Task

 Task是从 .NET Framework 4 推出的实现多线程的方法,Task 的使用非常简单,一行代码就可以开始一个异步任务,代码如下:

1
2
3
4
5
6
static void Main(string[] args)
{
    Console.WriteLine($"Task 示例,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}");
    Task.Run(() => Console.WriteLine($"Task 开启的线程,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}"));
    Console.ReadKey();
}

 向新创建的Task中传递参数的示例代码:

1
2
3
4
5
6
7
8
9
10
11
static void Main(string[] args)
    {
        Console.WriteLine($"Task 示例,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}");
 
            string temp = "Tanyongjun";
        
            Task t = new Task(obj => Console.WriteLine($"Task 开启的线程,传递进来的参数:{obj},当前线程的ID:{Thread.CurrentThread.ManagedThreadId}"), temp);
            t.Start();
 
        Console.ReadKey();
    }

 返回一个 int 类型结果的示例代码: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void Main(string[] args)
        {
            Console.WriteLine($"Task 示例,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}");
 
            Task<int> t = new Task<int>(() => {
                int sum = 0;
                for (int i = 0; i < 10; i++)
                {
                    sum += i;
                }
                return sum;
            });
            t.Start();
            Console.WriteLine($"使用Task开启新线程计算的结果:{t.Result}");
 
            Console.ReadKey();
        }

 

使用 TaskFactory 开始异步任务

1
2
3
4
5
6
7
8
9
10
11
12
13
static void Main(string[] args)
        {
            List<Task<int>> tasks = new List<Task<int>>();
            TaskFactory factory = new TaskFactory();
            tasks.Add(factory.StartNew<int>(() => { return 123; }));
            tasks.Add(factory.StartNew<int>(() => { return 456; }));
            foreach (var item in tasks)
            {
                Console.WriteLine($"返回值:{item.Result}");
            }
 
            Console.Read();
        }

上面的代码使用 TaskFactory 创建并运行了两个异步任务,同时把这两个任务加入了任务列表 tasks 中,然后使用foreach遍历出执行的结果。代码执行效果如下:

 

 用Task来模拟一个项目开发的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static void Main(string[] args)
{
 
    Console.WriteLine($"项目经理启动一个项目。线程ID:{Thread.CurrentThread.ManagedThreadId} ");
    Console.WriteLine($"前期准备工作。线程ID:{Thread.CurrentThread.ManagedThreadId} ");
    Console.WriteLine($"开始编程。线程ID:{Thread.CurrentThread.ManagedThreadId} ");
 
    List<Task> tasks = new List<Task>();//存放新开启的Task
    tasks.Add(Task.Run(() => Coding("张三", "后端")));
    tasks.Add(Task.Run(() => Coding("李四", "前端")));
    tasks.Add(Task.Run(() => Coding("王五", "测试")));
 
    // 阻塞当前线程,等着某一个任务执行完成后,才进入下一行。
    Task.WaitAny(tasks.ToArray());
    Console.WriteLine($"************** 项目里程碑。。线程ID:{Thread.CurrentThread.ManagedThreadId} **************");
 
    //等待1秒之后,执行某个动作
    //Task.WaitAll(tasks.ToArray(), 1000);
    //Console.WriteLine($"************** 已经等待了1秒啦。。线程ID:{Thread.CurrentThread.ManagedThreadId} **************");
 
    // 阻塞当前线程,等着全部任务完成后,才进行下一行。
    Task.WaitAll(tasks.ToArray());
    Console.WriteLine($"部署到正式环境,上线使用。线程ID:{Thread.CurrentThread.ManagedThreadId} ");
 
 
    Console.Read();
}
 
private static void Coding(string name, string project)
{
    Console.WriteLine($"Coding {name} 开始 {project}。线程ID:{Thread.CurrentThread.ManagedThreadId}。");
    int temp = 0;
    for (int i = 0; i < 1000000000; i++)
    {
        temp += i;
    }
    Console.WriteLine($"Coding {name} 结束 {project}。线程ID:{Thread.CurrentThread.ManagedThreadId}。");
 
}

代码执行效果如下:

 

Task 内部提供多种多样的基于队列的链式任务管理方法,通过使用这些快捷方式,可以让异步队列有序的执行,例如:ContinueWith(),ContinueWhenAll(),ContinueWhenAny(),WaitAll(),WaitAny(),WhenAll(),WhenAny()。

线程中的异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static void Main(string[] args)
        {
            try
            {
                TaskFactory taskFactory = new TaskFactory();
                List<Task> taskList = new List<Task>();
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Action<object> act = t => {
                        Thread.Sleep(1000);
                        if (k == 3)
                            throw new Exception("出现异常啦!");
                        Console.WriteLine($"程序正常 -- {k}。");
                    };
                    taskList.Add(taskFactory.StartNew(act, k));
                }
            }
            catch (AggregateException aex)
            {
                foreach (var item in aex.InnerExceptions)
                {
                    Console.WriteLine(item.Message);
                }               
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
 
            Console.ReadKey();
        }

  上面的代码执行结果:

在vs调试的时候能看到出现异常,但在从执行结果上看不到异常信息。这是因为线程里面的异常是抓不到的,它已经脱离try/catch 的范围。所以在新的线程内部添加try/catch ,也就是Action 具体操作内容加上try/catch,让Action不会出现异常。

上面的代码改动如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static void Main(string[] args)
        {
            try
            {
                TaskFactory taskFactory = new TaskFactory();
                List<Task> taskList = new List<Task>();
                for (int i = 0; i < 5; i++)
                {
                    int k = i;
                    Action<object> act = t => {
                        try
                        {
                            Thread.Sleep(1000);
                            if (k == 3)
                                throw new Exception("出现异常啦!");
                            Console.WriteLine($"程序正常 -- {k}。");
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
 
                    };
                    taskList.Add(taskFactory.StartNew(act, k));
                }
            }
            catch (AggregateException aex)
            {
                foreach (var item in aex.InnerExceptions)
                {
                    Console.WriteLine(item.Message);
                }               
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
 
            Console.ReadKey();
        }

  执行效果:

 线程安全

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void Main(string[] args)
{
    TaskFactory taskFactory = new TaskFactory();
    List<Task> taskList = new List<Task>();
    List<int> intList = new List<int>();
    int count = 0;
 
    for (int i = 0; i < 10000; i++)
    {
        int k = i;
        taskList.Add(taskFactory.StartNew(() =>
        {
            count += 1;
            intList.Add(k);
        }));
    }
    Task.WaitAll(taskList.ToArray());
    Console.WriteLine($"count={count}");
    Console.WriteLine($"intList中元素的个数={intList.Count()}");
 
    Console.ReadKey();
}

 执行效果:  

从执行后的结果来看两个都不是10000,这是因为有的线程丢失了。
多个线程同时操作一个变量(包括:都能访问的局部变量、全局变量、数据库中的一个值),就会有某个操作会被覆盖的可能。

想要解决上面出现的问题,我们可以通过加lock 来实现。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//private:防止外面使用;static:保障全局唯一;readonly:不能改动;object:引用类型
private static readonly object threadLock = new object();
static void Main(string[] args)
{
    TaskFactory taskFactory = new TaskFactory();
    List<Task> taskList = new List<Task>();
    List<int> intList = new List<int>();
    int count = 0;
 
    for (int i = 0; i < 10000; i++)
    {
        int k = i;
        taskList.Add(taskFactory.StartNew(() =>
        {
 
            lock (threadLock)
            {
                // 加lock 后就能保障任意时刻只有一个线程来执行该代码段
                count += 1;
                intList.Add(k);
            }
            //lock (this)
            //{
            //    // 使用this 要注意每次实例化是不同的锁,同一个实例是相同的锁。
            //    // 但是这个实例别人也能访问到,别人也能锁定。 所以一般不要使用
            //}
 
        }));
    }
    Task.WaitAll(taskList.ToArray());
    Console.WriteLine($"count={count}");
    Console.WriteLine($"intList中元素的个数={intList.Count()}");
 
    Console.ReadKey();
}

  执行效果:

使用lock 能解决线程线程安全,因为使用lock后只有一个线程可以进去,没有并发。虽然能解决问题,但是牺牲了性能。所以在使用lock来解决线程冲突的问题是要尽量缩小lock的范围。这种问题的最佳解决方案是,进行数据拆分,避免冲突。

 await/async

在.NET Framework4.5框架、C#5.0语法中,新增加了async和await两个关键字。
async 用来标记一个方法为异步方法,方法体内需结合 await 关键字使用。异步方法命名规则通常以Async结尾。await 关键字只能在异步方法中使用。它出现在 Task 前面。
async和await 一般都是成对出现的,只有async 是没有意义的,只有await 就会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void Main(string[] args)
        {
 
            Console.WriteLine("执行前Main的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
            TestAsync();
            Console.WriteLine("执行结束Main的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
 
            Console.ReadKey();
        }
 
        private static async void TestAsync()
        {
            Console.WriteLine("执行前TestAsync的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
             
            await Task.Run(()=> {
                Console.WriteLine("执行 Action 中Sleep之前 的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
                Thread.Sleep(3000);
                Console.WriteLine("执行 Action 中Sleep之后 的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
            });
 
            Console.WriteLine("执行结束TestAsync的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }

  执行效果:

 

  

posted @   #谭  阅读(108)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示