C#多线程之Task

2024-04-10  本文已影响0人  小羊爱学习

一 简介:

多线程的原理我就不讲了,做开发处处用到。C#也提供了几种多线程的使用方式,分别是Thread类、ThreadPool类、Parallel 类和Task类,这里只讲Task,其他的知道有就可以了,就像当初做iOS开发的时候,也把苹果原生的几种线程都学了,也做了博客记录,但是在实际开发当中这么多年却只用了GCD这一种方式来处理,问就是好用、性能高。就像人吃饭一样,有肉不吃干嘛非要去喝汤,对吧?

二 Task介绍

Task 是 .NET 中用于表示异步操作的类,出现在C#4.0时代,可以简单看作相当于Thead+TheadPool,其性能比直接使用Thread要好太多,它可以适用于很多种场景和功能。
比如:

总之,Task在多线程开发中起着非常重要的作用,一定要学好并用好。

三 Task开启方式

            Console.WriteLine("当前线程1:{0}", Thread.CurrentThread.ManagedThreadId);
            Task task1 = new Task(() =>// 无返回值
            {
                Console.WriteLine("当前线程2:{0}", Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(2000);
            });
            task1.Start();
            Console.WriteLine("主线程不受影响");

            Task<string> task2 = new Task<string>(() => {//  有返回值string类型
                return "task2";
            });
            task2.Start();
            string str = task2.Result;
            Console.WriteLine(str);
            Task.Run(() =>
            {
                Console.WriteLine("这里是子线程喽");
            });
            Task<string> task = Task.Run<string>(() =>
            {
                return"这里是子线程喽";
            });
            string str = task.Result;
            Console.WriteLine($"{str}");
            TaskFactory factory = Task.Factory;
            factory.StartNew(() =>
            {
                Console.WriteLine("你好 task");
            });

            Task<string> task = factory.StartNew<string>(() =>
            {
                return "你好 task";
            });
            string str = task.Result;
            Console.WriteLine($"{str}");

四 Task线程等待(阻塞线程)

            Task task1 = Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("我排第二");
            });
            Console.WriteLine("我先打印");
            task1.Wait(2000); //等待2000毫秒后再往下走 如果写task1.Wait()就是必须要等task1子线程走完才能往下走(那我有必要开子线程嘛)
            Task task2 = Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("我垫底了嘛");
            });
            task2.Wait(500);
            Console.WriteLine("我当老三");
            Console.WriteLine("来任务咯");
            Task task1 = Task.Run(() => {
                Thread.Sleep(1000);
                Console.WriteLine("我耗时1秒");
            });
            Task task2 = Task.Run(() => {
                Thread.Sleep(2000);
                Console.WriteLine("我耗时2秒");
            });
            Task.WaitAll(task1, task2);// 等待task1和task2任务全走完才能往后走
            Console.WriteLine("没事,我等你们走完我再走");
            Console.WriteLine("来任务咯");
            Task task1 = Task.Run(() => {
                Thread.Sleep(1000);
                Console.WriteLine("我耗时1秒");
            });
            Task task2 = Task.Run(() => {
                Thread.Sleep(2000);
                Console.WriteLine("我耗时2秒");
            });
            Task.WaitAny(task1, task2);// task1和task2只要有一个任务完成就往后走
            Console.WriteLine("只要有一个任务完成我就走喽");

五 Task任务的延续

            Task<int> task1 = Task.Run<int>(() => { return 1; });
            Task<int> task2 = Task.Run<int>(() => { return 2; });
            TaskAwaiter<int> task1Awaiter = task1.GetAwaiter();
            TaskAwaiter<int> task2Awaiter = task2.GetAwaiter();
            Task<int> task = Task.Run<int>(() =>
            {
                return task1Awaiter.GetResult() + task2Awaiter.GetResult();
            });
            Console.WriteLine($"让我来看看你们给的数字和是:{task.Result}");
            Console.WriteLine("会阻塞主线程哦");
            // WhenAll WhenAny + ContinueWith 不阻塞主线程
            Task<int> task1 = Task.Run<int>(() => { Thread.Sleep(5000); return 1; });
            Task<int> task2 = Task.Run<int>(() => { Thread.Sleep(5000); return 2; });
            Task<String> task = Task<int>.WhenAll(task1, task2).ContinueWith((t) => {
                // t:ContinueWith的返回值,一个新的延续task
                return "3";
            });
            Console.WriteLine($"我立马打印,对我没影响");
            Console.WriteLine($"我要等task走完才能打印哦:{task.Result}");
            Console.WriteLine($"上面堵了,我也要等了");
        }

六 Task枚举

Task parentTask = new Task(() => {
    Task task1 = new Task(() => { Console.WriteLine("task1任务。。。。。。"); }, TaskCreationOptions.AttachedToParent);
    Task task2 = new Task(() => { Console.WriteLine("task2任务。。。。。。"); }, TaskCreationOptions.AttachedToParent);
    task1.Start();
    task2.Start();
});
parentTask.Start();
parentTask.Wait();
Console.WriteLine("我是主线程");

默认情况下,新建Task线程是从线程池ThreadPool中分配出来的,当使用TaskCreationOptions.LongRunning声明后则是直接新建一个线程。这样就可以避免耗时任务一直占用线程池资源的情况。当然了,也可以直接使用Thread,效果上是一样的。

            Task task = new Task(() => { ...}, TaskCreationOptions.LongRunning);
            task.Start();

六 Task中使用取消令牌

            // Task任务的取消和判断
            CancellationTokenSource cst = new CancellationTokenSource();
            Task task = Task.Run(() => {
                while (!cst.IsCancellationRequested)
                {
                    Console.WriteLine("持续时间:" + DateTime.Now);
                }
            }, cst.Token);//这里第二个参数传入取消令牌

            Thread.Sleep(2000);
            cst.Cancel(); //两秒后结束
            // 任务的延时取消可以用于访问超时、执行超时等情况下的任务强制终止
            CancellationTokenSource cst = new CancellationTokenSource();
            Task task = Task.Run(() => {
                while (!cst.IsCancellationRequested)
                {
                    Console.WriteLine("持续时间:" + DateTime.Now);
                }
            }, cst.Token);//这里第二个参数传入取消令牌
            cst.CancelAfter(2000); //两秒后结束 也是异步进行
            // Task任务取消回调:如果取消任务后希望做一些处理工作。
            // 此时可以使用CancellationToken类的Register()函数来注册一个委托(回调函数),用于取消线程后调用。
            CancellationTokenSource cst = new CancellationTokenSource();
            Task task = Task.Run(() => {
                while (!cst.IsCancellationRequested)
                {
                    Console.WriteLine("持续时间:" + DateTime.Now);
                    Thread.Sleep(500);
                }
            }, cst.Token);//这里第二个参数传入取消令牌
            cst.Token.Register(() => {
                Console.WriteLine("开始处理工作......");
                Thread.Sleep(2000);
                Console.WriteLine("处理工作完成......");
            });
            Thread.Sleep(2000);
            cst.Cancel(); //两秒后结束
        private Task task;
        private CancellationTokenSource cancellationSource;

        private void TestTokenSource()
        {
            // 常规使用
            cancellationSource = new CancellationTokenSource();
            CancellationToken token = cancellationSource.Token;
            task = Task.Run(() =>
            {
                while (!token.IsCancellationRequested)
                {
                    Console.WriteLine("持续工作");
                }
            }, token);
            task.Wait(5000);
            cancellationSource?.Cancel();
            cancellationSource?.Dispose();
        }
            cancellationSource = new CancellationTokenSource();
            cancellationSource.Cancel();
            cancellationSource.Dispose();


            if (cancellationSource.Token.IsCancellationRequested)// 报错 System.ObjectDisposedException:“CancellationTokenSource 已释放。”
            {
                Console.WriteLine("cancle");
            }
            else
            {
                Console.WriteLine("not cancle");
            }


            if (cancellationSource.IsCancellationRequested)
            {
                Console.WriteLine("cancle");// 打印
            }
            else
            {
                Console.WriteLine("not cancle");
            }


            if (cancellationSource != null)
            {
                Console.WriteLine("not null");// 打印
            }
            else
            {
                Console.WriteLine(" null");
            }

七 Task跨线程访问控件

在使用Winform或WPF编写程序时,经常会遇到跨线程访问控件的情况,除了使用Invoke和委托等方法外,还可以有以下两种解决方法。

            Task task = new Task(() =>
            {
                Thread.Sleep(5000);//模拟耗时处理
                txt_Info.Text = "test"; //此为文本控件
            });
            task.Start(TaskScheduler.FromCurrentSynchronizationContext());
            txt_Info.Text = "数据正在处理中......";
            txt_Info.Text = "数据正在处理中......";
            Task.Run(() =>
            {
                Thread.Sleep(5000);
            }).ContinueWith(t => {
                txt_Info.Text = "test";
            }, TaskScheduler.FromCurrentSynchronizationContext());

八 Task的异常处理

1.线程外部使用Wait:Task线程的异常处理不能直接将线程对象相关代码try-catch来捕获,那样是捕获不到异常的,因为开始异常还没发生,主线程已经执行完毕,需要通过调用线程对象的wait()函数,通过wait()函数来进行线程的异常捕获。此外,线程的异常会聚合到AggregateException异常对象中(AggregateException是专门用来收集线程异常的异常类),需要通过遍历该异常对象,获取正确的异常信息。如果捕获到线程异常之后,还想继续往上抛出,就需要调用AggregateException对象的Handle函数,并返回false。(Handle函数遍历了一下AggregateException对象中的异常)

            Task task1 = Task.Run(() =>
            {
                throw new Exception("线程1的异常抛出");
            });
            Task task2 = Task.Run(() =>
            {
                throw new Exception("线程2的异常抛出");
            });
            Task task3 = Task.Run(() =>
            {
                throw new Exception("线程3的异常抛出");
            });

            try
            {
                task1.Wait();
                task2.Wait();
                task3.Wait();
            }
            catch (AggregateException ex)
            {
                foreach (var item in ex.InnerExceptions)
                {
                    Console.WriteLine(item.Message);
                }
                //如果希望再将异常往外抛出,可以调用AggregateException的Handle函数
                //ex.Handle(p => false);
            }
            Console.Read();

2.线程内部可直接try-catch

            try
            {
                Task task = Task.Run(() =>
                {
                    try
                    {
                        int i = 0;
                        int j = 10;
                        int k = j / i; //尝试除以0,会异常
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine($"线程内异常{ex.Message}");
                    }
                });
            }
            catch (AggregateException aex)
            {
                foreach (var exception in aex.InnerExceptions)
                {
                    Debug.WriteLine($"线程不等待:异常{exception.Message}");
                }
            }
上一篇下一篇

猜你喜欢

热点阅读