八、多线程进阶学习二之Task

1、通过Task开启线程

/// <summary>
/// 如何开启一个线程?
/// </summary>
private void OpenThread()
{
    Task task1 = new Task(() =>
    {
        this.DoSomethingLong("new Task(Action)");
        Console.WriteLine($"通过new Task(Action)开启了一个线程,线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    });
    task1.Start();

    Task task2 = Task.Run(() =>
    {
        this.DoSomethingLong("Task.Run(Action)");
        Console.WriteLine($"通过Task.Run(Action)开启了一个线程,线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    });
    //注意到,相比于new Task(Action),Task.Run并没有task2.Run,但是线程依旧创建了
    Func<int> func = () => { return DateTime.Now.Year; };
    Task<int> task3 = Task.Run(func);
    int year = task3.Result;
    Console.WriteLine($"有返回值的任务  今年是 {year} 年");
    
    TaskFactory taskFactory1 = new TaskFactory();
    taskFactory1.StartNew(() =>
    {
        this.DoSomethingLong("new TaskFactory() taskFactory.StartNew(Action)");
        Console.WriteLine($"通过new TaskFactory() 然后taskFactory.StartNew(Action)开启了一个线程,线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    });
    
    TaskFactory taskFactory2 = Task.Factory;
    taskFactory2.StartNew(() =>
    {
        this.DoSomethingLong("Task.Factory taskFactory.StartNew");
        Console.WriteLine($"通过Task.Factory 然后taskFactory.StartNew(Action)开启了一个线程,线程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    });
}

启动线程

2、Task来源

/// <summary>
/// Task也是来自于线程池
/// </summary>
private void TaskSource()
{
    ThreadPool.SetMaxThreads(14, 14);//现在线程池中最多只有14个线程,进程中全局唯一
    //设置以后,最多就只能开启14个线程
    //但是结果显示,使用的线程数远小于14,是因为有的线程执行完后又被重复使用
    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}, threadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        }));
    }
    Task.WaitAll(tasklist.ToArray());
    Console.WriteLine($"最大线程为:{countlist.Distinct().Count()}");
}

Task也是来自于线程池

3、父子线程场景A

/// <summary>
/// 父子线程,场景A
/// 父线程在默认情况下不会等待子线程执行完毕,父线程不阻塞
/// 主线程执行,遇到父线程task,父线程task开启后,遇到task.Wait(),表示主线程要等待父线程task执行完毕
/// 在父线程中又开启了两个子线程task1和task2,这两个子线程操作都比较耗时
/// 父线程不会等待这两个耗时操作执行完
/// 分析执行结果可以明显看出父子线程之间的关系
/// </summary>
private void ParentChildThreadA()
{
    Task task = new Task(() =>
    {
        Console.WriteLine($"父线程task线程开始了   threadID={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Task task1 = new Task(() =>
        {
            Console.WriteLine($"task1 Start ID = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Thread.Sleep(1500);
            Console.WriteLine($"task2 Ene ID = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        });

        Task task2 = new Task(() =>
        {
            Console.WriteLine($"task1 Start ID = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Thread.Sleep(1500);
            Console.WriteLine($"task2 Ene ID = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        });

        task1.Start();
        task2.Start();
        Console.WriteLine($"父线程task结束了   threadID={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    });
    task.Start();
    task.Wait();
    Console.WriteLine($"UI线程   threadID = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}

父子线程场景A

4、父子线程场景B

/// <summary>
/// 父子线程场景B
/// 如果需要让父线程在等待的时候,也让子线程执行完毕再继续往后,该怎么办?
/// 线程附着
/// 子线程告诉父线程,我是你儿,UI线程也得等等我
/// </summary>
private void ParentChildThreadB()
{
    Task task = new Task(() =>
    {
        Console.WriteLine($"父线程task开始了 threadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");

        Task task1 = new Task(() =>
        {
            Console.WriteLine($"task1 Start ID = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Thread.Sleep(1500);
            Console.WriteLine($"task1 End ID = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        }, TaskCreationOptions.AttachedToParent);//表示task1附着于父线程task

        Task task2 = new Task(() =>
        {
            Console.WriteLine($"task2 Start ID = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Thread.Sleep(3000);
            Console.WriteLine($"task2 End ID = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        }, TaskCreationOptions.AttachedToParent);//表示task2附着于父线程task
        task1.Start();
        task2.Start();
        Console.WriteLine($"父线程task结束了 threadID={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    });
    task.Start();
    task.Wait();//单个线程的等待
                //子线程附着父线程的情况,此时UI线程会等待子线程执行完
                //如果子线程有耗时操作,此处会造成界面卡顿
    Console.WriteLine($"UI线程 threadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}

父子线程场景B

5、线程优先级

/// <summary>
/// 线程优先级
/// 优先执行也只是概率问题,只是提高了概率,在意外情况下,不一定会优先
/// </summary>
private void ThreadPriority()
{
    Task task = new Task(() =>
    {
        Console.WriteLine("这是开启了一个线程……");
    }, TaskCreationOptions.PreferFairness);
    //TaskCreationOptions.PreferFairness
    //提示System.Threading.Tasks.TaskScheduler以尽可能公平的方式安排任务
    //这意味着安排得越早的任务越有可能运行得越早,安排得越晚的任务越有可能运行得越晚。
    //TaskCreationOptions.LongRunning
    //表示在子线程中如果消耗了大量的资源,默认是允许的
    //允许长时间在子线程中去执行
    //如果该参数不设置,默认是TaskCreationOptions.None
    task.Start();
    task.Wait();
    Console.WriteLine($"UI线程   ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}

线程优先级

6、Sleep And Delay

/// <summary>
/// Sleep就是主线程等待了,Delay就是主线程派了一个小弟,告诉小弟,你等2s去做个事情
/// </summary>
private void SleepAndDelay()
{
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        Console.WriteLine("Sleep之前");
        Thread.Sleep(2000);//当前线程等待2000ms后继续往后执行
        Console.WriteLine("Sleep之后");
        stopwatch.Stop();
        Console.WriteLine($"Sleep 耗时 {stopwatch.ElapsedMilliseconds} ms");
    }
    {
        //Delay相当于计时多久,然后做事
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        Console.WriteLine("Delay之前");
        Task.Delay(2000);//当前线程等待2000ms后继续往后执行
        Console.WriteLine("Delay之后");
    }
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        Console.WriteLine("Delay.ContinueWith之前");
        Task.Delay(2000).ContinueWith(s =>
        {
            //相当于等待了2000ms之后,在执行这些任务
            stopwatch.Stop();
            Console.WriteLine($"Delay.ContinueWith 耗时 {stopwatch.ElapsedMilliseconds} ms");
            Console.WriteLine("这是一个延迟任务");
        });
        Console.WriteLine("Delay.ContinueWith之后");
    }
}

SleepAndDelay

7、线程ID和任务ID

/// <summary>
/// 线程Id和任务Id
/// </summary>
private void ThreadIdAndTaskId()
{
    Task t1 = Task.Run(() =>
    {
        Console.WriteLine($"1.1 ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}   " +
            $"任务Id = {Task.CurrentId}");
    });
    t1.Wait();
    Console.WriteLine($"1.2 ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}   " +
        $"任务Id = {t1.Id}");
    //Task.CurrentId和t1.Id都是任务Id,是对当前执行的任务进行编号
    //线程Id是对当前线程进行编号
    //外部Task.CurrentId取不到
    Task t2 = Task.Run(() =>
    {
        Console.WriteLine($"2.1 ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}   " +
            $"任务Id = {Task.CurrentId}");
    });
    t2.Wait();
    Console.WriteLine($"2.2 ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}  " +
            $"  任务Id = {t2.Id}");
}

线程ID和任务ID

8、应用场景举例

/// <summary>
/// 多线程在什么场景下使用?有什么意义?
/// 需要任务并发的时候就可以使用多线程
/// 提高性能,改善用户体验
/// </summary>
private void MultithreadingUsageScenario()
{
    Console.WriteLine("开始培训……");
    Console.WriteLine("老师讲课……");//一节课一节课地讲解课程
    this.Teach("lesson01");
    this.Teach("lesson02");
    this.Teach("lesson03");
    this.Teach("lesson04");
    Console.WriteLine("开始直播平台项目实战……");
    //同学们合作完成,可以使用多线程,每个同学对应一个线程
    List<Task> list = new List<Task>();
    TaskFactory factory = new TaskFactory();
    list.Add(factory.StartNew(() => this.Coding("小明", "WebAPI")));
    list.Add(factory.StartNew(() => this.Coding("小红", "视频流处理")));
    list.Add(factory.StartNew(() => this.Coding("小刚", "搭建集群")));
    list.Add(factory.StartNew(() => this.Coding("小兰", "搭建RabbitMQ异步队列")));

    //5.如果让红包奖励一定在环境部署前发放

    //4.项目大功告成,摆个庆功宴,不能卡顿界面
    factory.ContinueWhenAll(list.ToArray(), s =>
    {
        Console.WriteLine($"factory.ContinueWhenAll ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}   " +
            $"项目大功告成,摆个庆功宴");
    });

    //3.如果XX同学第一个完成功能,获取一个红包奖励,要求不能卡顿界面
    factory.ContinueWhenAny(list.ToArray(), s =>
    {
        //这其实就是一个回调,开启了一个新的线程执行任务
        //这个线程可能是新开启的,也有可能是上面执行的任务的线程
        Console.WriteLine($"factory.ContinueWhenAny ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}   " +
            $"xxx同学第一个完成任务,获取一个红包奖励");
    });

    //2.有一个人完成任务时,老师就要开始准备环境
    Task.WaitAny(list.ToArray());
    Console.WriteLine("有一个人完成任务,老师开始准备环境……");

    //1.整个项目完成以后,老师做点评
    //应该是所有任务都完成后,才开始做点评、颁奖
    Task.WaitAll(list.ToArray());//主线程等待所有子线程执行完成以后,往后执行,会卡顿界面
    Console.WriteLine("直播平台开发完成……老师做出点评……");

    //有哪些应用场景?
    //WaitAll:系统首页,有本周自然之星,本月业绩Top10,本年度……这些数据来自于不同数据库/第三方接口
    //后台获取数据时就可以去开启多个线程,同时去获取数据,等待所有数据获取到以后,统一返回给前端

    //WaitAny:商品类目,有可能来自于数据库/缓存/第三方接口,我们为了更高的性能,就可以开启多个线程
    //同时去查询数据,只要是有一个线程获取到数据了,其他的就不管了

    //以上两个都是卡顿界面的,能不能做到既不卡顿界面又高性能呢?
}

场景应用

9、控制线程数量

/// <summary>
/// 开启线程时,如何控制线程数量?
/// 不能基于ThreadPool设置
/// </summary>
private void ControlNumThread()
{
    List<Task> taskList = new List<Task>();
    //难道循环100次就开启100个线程吗?
    //如何限制线程在20个?
    for(int i = 0; i < 100; i++)
    {
        int k = i;
        //Task.RanToCompletion//任务已成功完成执行
        if(taskList.Count(t=>t.Status != TaskStatus.RanToCompletion) > 20)
        {
            Task.WaitAny(taskList.ToArray());
            taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList();
        }
        taskList.Add(Task.Run(() =>
        {
            Console.WriteLine($"开启的第 {k} 个线程,ThreadId = {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        }));
    }
}

private method

#region private method
private void DoSomethingLong(string name)
{
    Console.WriteLine($"DoSomethingLong Start  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    long lResult = 0;
    //for (int i = 0; i < 1000_000_000; i++)
    //{
    //    lResult += i;
    //}
    Thread.Sleep(2000); //线程是闲置的;
    Console.WriteLine($"DoSomethingLong   End  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}");
}

private void Teach(string lesson)
{
    Console.WriteLine($"{lesson}课程开始了……");

    Console.WriteLine($"{lesson}课程讲完了……");
}

private void Coding(string name, string projectName)
{
    Console.WriteLine($"Coding Start  {name} {projectName}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
    long lResult = 0;
    for (int i = 0; i < 1_000_000_000; i++)
    {
        lResult += i;
    }

    Console.WriteLine($"Coding   End  {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}");
}
#endregion
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值