C#多线程之Task

C#中的多线程Task

 一 、定义

msdn关于Task的解释如下:

Task类表示不返回值并且通常以异步方式执行的单个操作。 Task对象是在 .NET Framework 4 中首次引入的 基于任务的异步模式的中心组件之一。

Task在线程池线程上异步执行,可以通过Status属性以及 、IsCompleted 和 IsFaulted 属性来确定Task任务的状态。

通常,lambda 表达式用于指定任务要执行的工作。对于返回值的操作,请使用 Task<TResult>类.

二、开启Task线程的几种方式

通常启动Task任务的方式一般有以下几种:

        static void Main(string[] args)
        {
            Console.WriteLine($"Main线程ID为:{Thread.CurrentThread.ManagedThreadId}");
            //1.如何开启一个线程
            {
                Task task1 = new Task(() =>
                {
                    Console.WriteLine("第一个task线程,task start的方式启动");
                    Console.WriteLine($"task1线程ID为{Thread.CurrentThread.ManagedThreadId}");
                });
                task1.Start();
                task1.Wait();//等待当前线程执行完成后再往下执行。所以加上Wait,后面启动的线程会继续用这个ID线程。
            }
            {
                Task task2 = Task.Run(() =>
               {
                   Console.WriteLine("第二个task线程,通过直接Run的方式启动");
                   Console.WriteLine($"task2线程ID为{Thread.CurrentThread.ManagedThreadId}");
               });
                if (!task2.IsCompleted)
                {
                    Thread.Sleep(5000);
                    Console.WriteLine("睡眠了5s");
                    task2.Wait();
                }
            }
            {
                TaskFactory taskFactory = new TaskFactory();
                taskFactory.StartNew(() =>
                {

                    Console.WriteLine("第三个task线程,通过taskFactory的方式启动");
                    Console.WriteLine($"第三个线程ID为{Thread.CurrentThread.ManagedThreadId}");
                });
            }

此处因为执行了task.Wait()方法,其会等待当前线程执行完后再执行后续线程,所以后面两个线程的ID沿用之前ID 4的线程.

 三、线程池数目的设定

 当我们在线程池开启多个线程的时候,可以得知当前计算机支持的最大线程数【当前最大为5】。

            //2.Task线程也是来自于线程池
            #region Task线程也是来自于线程池
            List<int> countList = new List<int>();
            List<Task> taskList = new List<Task>();
            for (int i = 0; i < 100; i++)
            {
                int k = i;
                taskList.Add(Task.Run(() =>
                {
                    countList.Add(Thread.CurrentThread.ManagedThreadId);
                    Console.WriteLine($"k={k}");
                    Console.WriteLine($"线程ID为:{Thread.CurrentThread.ManagedThreadId}");
                }));
            }
            Task.WaitAll(taskList.ToArray());//等待所有的线程执行完毕
            Console.WriteLine($"最大线程数目为:{countList.Distinct().Count()}");
            //输出结果里面只有4,5,6,7,8这几个ID的线程,最大线程数目为5
            Console.ReadKey();

相关API信息:

public static bool SetMaxThreads (int workerThreads, int completionPortThreads);


workerThreads
Int32
线程池中辅助线程的最大数目。
completionPortThreads
Int32
线程池中异步 I/O 线程的最大数目。

 如果我通过SetMaxThreads设置的最大线程数目小于计算机的最大线程数时,则当前线程池分配的最大线程为设置的MaxThreads。

ThreadPool.SetMinThreads(2, 2);  //设置最小线程数为2个
ThreadPool.SetMaxThreads(3, 3);  //设置最大线程数为3个,这两个方法要配合使用才能控制线程数量

四、TaskCreationOptions位枚举进行线程控制

TaskCreationOptions.AttachedToParent 控制父子线程执行顺序

当在主线程中启动一个线程,记为A,当在这个A线程中再开启子线程B时,A线程默认情况下不会等待B线程执行完毕,A线程不阻塞。

    //3  a父子线程【task里面套task】,父线程在默认情况下不会等待子线程执行完毕;父线程不阻塞
            {
                Task task = Task.Run(() =>
                {
                    Console.WriteLine($"父线程内部开始执行,ID为{Thread.CurrentThread.ManagedThreadId}");
                    Task task1 = Task.Run(() =>
                    {
                        Thread.Sleep(1000);
                        Console.WriteLine($"task1子线程内部开始执行,线程ID为{Thread.CurrentThread.ManagedThreadId}");
                        Thread.Sleep(1000);
                        Console.WriteLine("task1子线程sleep 1s");
                    });
                    Task task2 = Task.Run(() =>
                      {
                          Console.WriteLine($"task2子线程内部开始执行,线程ID为{Thread.CurrentThread.ManagedThreadId}");
                          Thread.Sleep(1000);
                          Console.WriteLine("task2子线程sleep 1s");

                      });
                });
                task.Wait();//等待父线程执行完毕后再往下执行
                Console.WriteLine("父线程执行完毕");
            }

结果为:

 如果我想让父线程在等待的时候,也让子线程执行完毕后继续往后执行,如何处理呢?

可以采用线程附着的方式。

Task提供了这样的API:

//
        // 摘要:
        //     Initializes a new System.Threading.Tasks.Task with the specified action and creation
        //     options.
        //
        // 参数:
        //   action:
        //     The delegate that represents the code to execute in the task.
        //
        //   creationOptions:
        //     The System.Threading.Tasks.TaskCreationOptions used to customize the task's behavior.
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     The action argument is null.
        //
        //   T:System.ArgumentOutOfRangeException:
        //     The creationOptions argument specifies an invalid value for System.Threading.Tasks.TaskCreationOptions.
        public Task(Action action, TaskCreationOptions creationOptions);
View Code
//b如果需要让父线程在等待的时候,也让子线程执行完毕,继续往后,怎么办??【线程附着】
            {
                //此时开始线程不是用task.run而是实例化一个task然后start
                Task task = new Task(() =>
                {
                    Console.WriteLine($"父线程内部开始执行,ID为{Thread.CurrentThread.ManagedThreadId}");
                    Task task1 = new Task(
                        () =>
                    {
                        //Thread.Sleep(1000);
                        Console.WriteLine($"task1子线程内部开始执行,线程ID为{Thread.CurrentThread.ManagedThreadId}");
                        Thread.Sleep(1000);//task1 sleep时,task2拿到控制权就开始执行了
                        Console.WriteLine("task1子线程sleep 1s");
                    }, TaskCreationOptions.AttachedToParent
                    );
                    Task task2 = new Task(() =>
                    {
                        Console.WriteLine($"task2子线程内部开始执行,线程ID为{Thread.CurrentThread.ManagedThreadId}");
                        Thread.Sleep(1000);
                        Console.WriteLine("task2子线程sleep 1s");

                    }, TaskCreationOptions.AttachedToParent);
                    task1.Start();
                    task2.Start();
                });
                task.Start();
                task.Wait();//等待父线程执行完毕后再往下执行
                Console.WriteLine("父线程执行完毕");

结果如下:

TaskCreationOptions.PreferFairness 指定线程池中当前线程执行优先级

            //4 线程优先级: 这里优先执行也只是概率问题,只是提高了效率,在意外情况下也不是一定优先
            {
                Task task = new Task(() =>
                    {
                        Console.WriteLine("线程优先级");
                    }, TaskCreationOptions.PreferFairness);//指定优先级(但是也只是提高概率,在意外情况下也不一定优先)
            }

TaskCreationOptions.LongRunning 创建的任务将会脱离线程池启动一个单独的线程来执行

C#启动的Task都会通过TaskScheduler来安排执行。根据官方文档的描述:

The default scheduler for the Task Parallel Library and PLINQ uses the .NET Framework thread pool, which is represented by the ThreadPool class, to queue and execute work. The thread pool uses the information that is provided by the Task type to efficiently support the fine-grained parallelism (short-lived units of work) that parallel tasks and queries often represent.

而默认的TaskScheduler采用的是.NET线程池ThreadPool,它主要面向的是细粒度的小任务,其执行时间通常在毫秒级。线程池中的线程数与处理器的内核数有关,如果线程池中没有空闲的线程,那么后续的Task将会被阻塞。因此,如果事先知道一个Task的执行需要较长的时间,就需要使用TaskCreationOptions.LongRunning枚举指明。使用TaskCreationOptions.LongRunning创建的任务将会脱离线程池启动一个单独的线程来执行。

   {
                Task task = new Task(() =>
                {
                    Console.WriteLine("这里新开了一个线程");
                }, TaskCreationOptions.LongRunning);//在子线程中如果消耗了大量的资源,默认是允许的,允许长时间在子线程去执行
    }

五、Task任务状态控制

Task.Delay 延时执行指定任务

             //这里Delay相当于计时2s后再执行里面的打印语句
                Task.Delay(2000).ContinueWith(s =>
                {

                    stopwatch.Stop();
                    Console.WriteLine($"delay之后消耗时间:{ stopwatch.ElapsedMilliseconds}");
                    Console.WriteLine("这是一个延迟任务");

                });

注意其与Sleep的区别:

Thread.Sleep(2000);//当前线程等待2s后,当获取时间片后才继续往后执行

 Task.WaitAll 阻塞当前线程,等待所有任务执行完成

//等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程。
public static void WaitAll(params Task[] tasks);
//等待所有提供的可取消 System.Threading.Tasks.Task 对象在指定的时间间隔内完成执行。
public static bool WaitAll(Task[] tasks, TimeSpan timeout);
//等待所有提供的 System.Threading.Tasks.Task 在指定的毫秒数内完成执行。
public static bool WaitAll(Task[] tasks, int millisecondsTimeout);
//等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程(除非取消等待)。
public static void WaitAll(Task[] tasks, CancellationToken cancellationToken);
//等待提供的所有 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行,或等到取消等待。
public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);
private static void TestWaitAll()
        {
            Task t1 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("Excute task1");
            });

            Task t2 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("Excute task2");
            });
            Task.WaitAll(t1, t2);
            //Task.WaitAll(new Task[] { t1, t2 }, 200);
            //Task.WaitAll(new Task[] { t1, t2 }, TimeSpan.FromMilliseconds(200));
            Console.WriteLine("main");
        }

 Task.WaitAny 阻塞当前线程,等待任一任务执行完成

//等待提供的任一 System.Threading.Tasks.Task 对象完成执行过程。
public static int WaitAny(params Task[] tasks);
//等待任何提供的 System.Threading.Tasks.Task 对象在指定的时间间隔内完成执行。
public static int WaitAny(Task[] tasks, TimeSpan timeout);
//等待任何提供的 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行。
public static int WaitAny(Task[] tasks, int millisecondsTimeout);
//等待提供的任何 System.Threading.Tasks.Task 对象完成执行过程(除非取消等待)。
public static int WaitAny(Task[] tasks, CancellationToken cancellationToken);
//等待提供的任何 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行,或等到取消标记取消。
public static int WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);
private static void TestWaitAny()
        {
            Task task1 = Task.Run(() =>
                {
                    Console.WriteLine("task1 run");
                    Console.WriteLine($"task1 thread id is {Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(100);
                }
                );
            Task task2 = new Task(() =>
                {
                    Console.WriteLine("task2 run");
                    Console.WriteLine($"task2 thread id is {Thread.CurrentThread.ManagedThreadId}");
                }
                );
            Task.WaitAny(task1, task2);
            Console.WriteLine("main thread run");
            Console.WriteLine($"main thead id is {Thread.CurrentThread.ManagedThreadId}");
        }

 WhenAll 不阻塞当前线程,等待所有任务执行完成后,可以进行ContinueWith

不带返回值
private static void TestWhenAll()
        {
            Task t1 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("Excute task1");
            });

            Task t2 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("Excute task2");
            });
            Task.WhenAll(t1, t2).ContinueWith(t=> {
                Console.WriteLine("after Excute task1,task2,execute ContinueWith ..");
            });
            Console.WriteLine("main");
        }

带返回值
private static void TestWhenAllWithReturn()
        {
            Task<string> t1 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(300);
                Console.WriteLine("Excute task1");
                return "return task1 result";
            });

            Task<string> t2 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("Excute task2");
                return "return task2 result";
            });
            //此处的t为t1,t2的集合,
            Task.WhenAll(new Task<string>[] { t1, t2 }).ContinueWith(t =>
            {
                string s = string.Empty;
                foreach (var item in t.Result)
                {
                    s += item;
                    s += " ";
                }

                Console.WriteLine(s);
            });
            Console.WriteLine("TestWhenAllWithReturn");
        }

 

WhenAny 不阻塞当前线程,等待任一任务执行完成后,可以进行ContinueWith

//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task> WhenAny(params Task[] tasks);
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task> WhenAny(IEnumerable<Task> tasks);
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks);

Main方法执行下面的方法:

public static Task<Task> WhenAny(params Task[] tasks);//不带返回值
    private static void TestWhenAny()
        {
            Task t1 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(300);
                Console.WriteLine("Excute task1");
            });

            Task t2 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("Excute task2");
            });
            Task.WhenAny(t1, t2).ContinueWith(t =>
            {
                Console.WriteLine("after Excute WhenAny task1,task2,execute ContinueWith ..");
            });
            Console.WriteLine("TestWhenAny");
        }

public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);//带返回值
private static void TestWhenAnyWithReturn()
        {
            Task<string> t1 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(300);
                Console.WriteLine("Excute task1");
                return "return task1 result";
            });

            Task<string> t2 = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("Excute task2");
                return "return task2 result";
            });
            Task.WhenAny(new Task<string>[] { t1, t2 }).ContinueWith(t =>
            {
                t.Result.ContinueWith(tt =>
                {
                    Console.WriteLine(tt.Result);
                });
            });
            Console.WriteLine("TestWhenAnyWithReturn");
        }

ContinueWith方法 创建一个在目标 Task完成时异步执行的延续任务

相当于回调

1.使用lambda表达式方式

private static void ContinueWithTest()
        {
            Console.WriteLine("start");
            Task<string> task1 = new Task<string>(() =>
            {
                Console.WriteLine("task0");
                return "task1";
            });
            Task<string> task2 = task1.ContinueWith(t =>
            {
                //task1执行完后执行task2里面的逻辑,所以
                //一定能在该语句里面拿到task1的返回值即t.Result,且与主线程异步
                Console.WriteLine(t.Result);
                return "task2";
            });
            task1.Start();
            Console.WriteLine("end");
        }

2.使用函数

    private static void ContinueWithTest1()
        {
            Console.WriteLine("start");

              // Func<string> func = new Func<string>(DoTask1);

             //或Func<string> func=DoTask1
            // Task<string> task1 = new Task<string>(func);写成这样也是与下面要实现的功能一样

            Task<string> task1 = new Task<string>(DoTask1);
            Task<string> task2 = task1.ContinueWith(DoTask2);
            task1.Start();
            Console.WriteLine("end");
        }

        private static string DoTask1()
        {
            Console.WriteLine("task0");
            return "task1";
        }

        private static string DoTask2(Task<string> t)
        {
            Console.WriteLine(t.Result);
            return "task2";
        }

TaskFactory

StartNew:创建并启动任务

ContinueWhenAll:创建一个延续任务,该任务在一组指定的任务完成后开始

ContinueWhenAny:创建一个延续 System.Threading.Tasks.Task,它将在提供的组中的任何任务完成后马上开始

FromAsync:创建一个 System.Threading.Tasks.Task`1,表示符合异步编程模型模式的成对的开始和结束方法。

參考:https://www.cnblogs.com/yaosj/p/10342883.html

 

posted @ 2021-01-07 22:43  liweiyin  阅读(3976)  评论(0编辑  收藏  举报