.NET异步和多线程系列(三)- Task和Parallel

一、Task类

Task是.NET Framework 3.0出现的,线程是基于线程池的,然后提供了丰富的API。Task被称之为多线程的最佳实践

首先我们来看下如何使用Task来启动线程:

/// <summary>
/// 一个比较耗时耗资源的私有方法
/// </summary>
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 < 1_000_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}***************");
}
/// <summary>
/// Task是.NET Framework 3.0出现的,线程是基于线程池的,然后提供了丰富的API。
/// </summary>
private void btnTask_Click(object sender, EventArgs e)
{
    Console.WriteLine($"****************btnTask_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

    {
        Task task = new Task(() => this.DoSomethingLong("btnTask_Click_1"));
        task.Start();
    }

    {
        Task task = Task.Run(() => this.DoSomethingLong("btnTask_Click_2"));
    }

    {
        TaskFactory taskFactory = Task.Factory;
        Task task = taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_3"));
    }

    Console.WriteLine($"****************btnTask_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}

Task的线程是源于线程池,线程池是单例的,全局唯一的。

//Task的线程是源于线程池
//线程池是单例的,全局唯一的
{
    //设置的最大值,必须大于CPU核数,否则设置无效
    //全局的,请不要这样设置!!!此处设置只是为了演示
    ThreadPool.SetMaxThreads(12, 12);

    //设置后,同时并发的Task只有12个;而且线程是复用的;
    for (int i = 0; i < 100; i++)
    {
        int k = i;
        Task.Run(() =>
        {
            Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Thread.Sleep(2000);
        });
    }
    //假如说我想控制下Task的并发数量,该怎么做?
}

注意:线程池的线程数量,设置的最大值,必须大于CPU核数,否则设置无效。

运行结果如下:

从结果中可以看出同时并发的Task只有12个(线程ID从03到14);而且线程是复用的;

同步等待:

{
    Stopwatch stopwatch = new Stopwatch(); //计时
    stopwatch.Start();
    Console.WriteLine("在Sleep之前");
    Thread.Sleep(2000);//同步等待--当前线程等待2s 然后继续
    Console.WriteLine("在Sleep之后");
    stopwatch.Stop();
    Console.WriteLine($"Sleep耗时{stopwatch.ElapsedMilliseconds}");
}

异步等待(Task.Delay方法,一般结合ContinueWith一起用),如下所示:

{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    Console.WriteLine("在Delay之前");
    Task task = Task.Delay(2000) //异步等待--等待2s后启动新任务
        .ContinueWith(t =>
        {
            stopwatch.Stop();
            Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");

            Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        });
    Console.WriteLine("在Delay之后");
    //stopwatch.Stop();
    //Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");
}

什么时候能用多线程? 任务能并发的时候。多线程能干嘛?提升速度/优化用户体验。

ContinueWhenAny:同时并发多个任务,当其中任意一个任务完成的时候,也可能是多个任务同时第一时间完成的时候,执行后续的任务。非阻塞式的回调

ContinueWhenAll:同时并发多个任务,当所有的任务都完成的时候,执行后续的任务。非阻塞式的回调

WaitAny:阻塞当前线程,等着任意一个任务完成。

WaitAll:阻塞当前线程,等着全部任务完成。

/// <summary>
/// 模拟讲课
/// </summary>
private void Teach(string lesson)
{
    Console.WriteLine($"{lesson}开始讲。。。");
    long lResult = 0;
    for (int i = 0; i < 1_000_000_000; i++)
    {
        lResult += i;
    }
    Console.WriteLine($"{lesson}讲完了。。。");
}

/// <summary>
/// 模拟编程过程
/// </summary>
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}***************");
}
{
    //什么时候能用多线程? 任务能并发的时候
    //多线程能干嘛?提升速度/优化用户体验
    Console.WriteLine("Eleven开启了一学期的课程");
    this.Teach("Lesson1");
    this.Teach("Lesson2");
    this.Teach("Lesson3");
    //不能并发,因为有严格顺序(只有Eleven讲课)
    Console.WriteLine("部署一下项目实战作业,需要多人合作完成");

    //开发可以多人合作---多线程--提升性能
    TaskFactory taskFactory = new TaskFactory();
    List<Task> taskList = new List<Task>();
    taskList.Add(taskFactory.StartNew(() => this.Coding("张三", "Portal")));
    taskList.Add(taskFactory.StartNew(() => this.Coding("李四", "DBA ")));
    taskList.Add(taskFactory.StartNew(() => this.Coding("王五", "Client")));
    taskList.Add(taskFactory.StartNew(() => this.Coding("赵六", "BackService")));
    taskList.Add(taskFactory.StartNew(() => this.Coding("钱七", "Wechat")));

    //谁第一个完成,获取一个红包奖励
    taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"XXX开发完成,获取个红包奖励" +
        $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
    //实战作业完成后,一起庆祝一下
    taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下" +
        $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}")));
    //ContinueWhenAny  ContinueWhenAll 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程。

    //阻塞当前线程,等着任意一个任务完成
    Task.WaitAny(taskList.ToArray());//也可以限时等待
    Console.WriteLine("Eleven准备环境开始部署");
    //需要能够等待全部线程完成任务再继续  阻塞当前线程,等着全部任务完成
    Task.WaitAll(taskList.ToArray());
    Console.WriteLine("5个模块全部完成后,Eleven集中点评");
    //Task.WaitAny  WaitAll都是阻塞当前线程,等任务完成后执行操作

    //阻塞卡界面,是为了并发以及顺序控制
    //网站首页:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,需要等待WaitAll
    //列表页:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪个先完成就用哪个结果,其他的就不管了
}

启动带有回调带有返回值的线程:

{
    //带有回调
    Task.Run(() => this.DoSomethingLong("btnTask_Click")).ContinueWith(t => Console.WriteLine($"btnTask_Click已完成" +
        $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
}

{
    //带有返回值
    Task<int> result = Task.Run<int>(() =>
        {
            Thread.Sleep(2000);
            return DateTime.Now.Year;
        });
    int i = result.Result;//会阻塞
}

控制Task的并发数量,该怎么做?(下文会介绍更好的解决方案)

{
    //假如说我想控制下Task的并发数量,该怎么做?  20个
    List<Task> taskList = new List<Task>();
    for (int i = 0; i < 10000; i++)
    {
        int k = i;
        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($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Thread.Sleep(2000);
        }));
    }
}

想使用返回值但又不想阻塞线程怎么办?

//想使用返回值但又不想阻塞线程怎么办?
{
    Task.Run<int>(() =>
    {
        Thread.Sleep(2000);
        return DateTime.Now.Year;
    }).ContinueWith(tInt =>
    {
        int i = tInt.Result; //不会阻塞
    });

    //不会阻塞
    Task<int> result = Task.Run<int>(() =>
    {
        Thread.Sleep(2000);
        return DateTime.Now.Year;
    });
    Task.Run(() =>
    {
        int i = result.Result;
    });
}

如果在启动线程的时候想带一些参数信息,则可以使用对应的重载方法:

{
    TaskFactory taskFactory = new TaskFactory();
    List<Task> taskList = new List<Task>();
    taskList.Add(taskFactory.StartNew(o => this.Coding("张三", "Portal"), "张三"));
    taskList.Add(taskFactory.StartNew(o => this.Coding("李四", "  DBA "), "李四"));
    taskList.Add(taskFactory.StartNew(o => this.Coding("王五", "Client"), "王五"));
    taskList.Add(taskFactory.StartNew(o => this.Coding(" 赵六", "BackService"), " 赵六"));
    taskList.Add(taskFactory.StartNew(o => this.Coding("钱七", "Wechat"), "钱七"));

    //谁第一个完成,获取一个红包奖励
    taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}开发完成,获取个红包奖励" +
        $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
}

几乎90%以上的多线程场景,以及顺序控制,以上的Task的方法就可以完成。

如果你的多线程场景太复杂搞不定,那么请梳理一下你的流程,简化一下。

建议最好不要线程嵌套线程,两层勉强能懂,三层hold不住的,更多只能求神。

 

二、Parallel类

Parallel可以启动多个线程去并发执行多个Action,它是多线程的。Parallel最直观的特点是主线程(当前线程)也会参与计算---阻塞界面(主线程忙于计算,无暇他顾)

Parallel是在Task的基础上做了一个封装,它的效果等于TaskWaitAll+主线程(当前线程)参与计算

/// <summary>
/// Parallel
/// </summary>
private void btnParallel_Click(object sender, EventArgs e)
{
    Console.WriteLine($"****************btnParallel_Click Start   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");

    {
        //Parallel并发执行多个Action 多线程的
        //主线程(当前线程)会参与计算---阻塞界面
        //等于TaskWaitAll+主线程计算
        Parallel.Invoke(
            () => this.DoSomethingLong("btnParallel_Click_1"),
            () => this.DoSomethingLong("btnParallel_Click_2"),
            () => this.DoSomethingLong("btnParallel_Click_3"),
            () => this.DoSomethingLong("btnParallel_Click_4"),
            () => this.DoSomethingLong("btnParallel_Click_5"));
    }

    Console.WriteLine($"****************btnParallel_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} " +
        $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}

运行结果如下:

可以看出线程ID为01的主线程(当前线程)也参与计算了。

Parallel其他的API如下所示:

//主线程(当前线程)会参与计算
{
    Parallel.For(0, 5, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
}

//主线程(当前线程)会参与计算
{
    Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
}

如何控制并发(线程)数量?上面我们在介绍Task的时候已经写了一种解决方案,其实还有一种更好的解决方案,就是使用Parallel来实现。

//控制线程数量(并发数量)
//会阻塞当前线程(主线程)-- 卡界面
{
    ParallelOptions options = new ParallelOptions();
    options.MaxDegreeOfParallelism = 3;
    Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
}

//控制线程数量(并发数量)
//不会阻塞当前线程(主线程)
{
    Task.Run(() =>
    {
        ParallelOptions options = new ParallelOptions();
        options.MaxDegreeOfParallelism = 3;
        Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
    });
}

 

Demo源码:

链接:https://pan.baidu.com/s/1SpbyPnohojyakxCOu4S9DA 
提取码:mwd1

此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/13556424.html

版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改,谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
.NET 中实现多线程高并发可以使用以下几种方法: 1. 使用 Thread 类:Thread 类是 .NET 提供的最基本的多线程编程类。你可以创建多个 Thread 实例,并在每个实例中执行不同的任务。但是需要注意线程同步和协作问题,避免出现竞态条件和死锁等问题。 2. 使用 ThreadPool 类:ThreadPool 是一个已经初始化的线程池,可以通过 QueueUserWorkItem 方法将任务加入到线程池中。线程池会自动管理线程的创建和回收,适用于大量短时间执行的任务。 3. 使用 Task Parallel Library(TPL):TPL 是 .NET Framework 4.0 引入的并行编程模型,它提供了更高级的抽象,通过 Task 类封装了多线程操作。你可以使用 Task.Factory.StartNew 方法创建和管理任务,并通过 Task.WaitAll 或 Task.WaitAny 等方法等待任务完成。 4. 使用 async/await 异步编程模型:.NET Framework 4.5 引入了异步编程模型,通过 async/await 关键字可以方便地进行异步操作。你可以使用 Task.Run 方法在后台线程执行耗时操作,同时使用 async/await 简化异步代码的编写。 5. 使用并发集合:.NET 提供了一些并发集合类,如 ConcurrentQueue、ConcurrentStack 和 ConcurrentDictionary 等,它们在多线程环境下提供了安全的并发访问。 在实现多线程高并发时,需要注意线程安全、资源共享和线程同步等问题,避免出现数据竞争和死锁等并发问题。可以使用锁、互斥量、信号量、事件等同步机制来保证线程安全和协作。同时,合理地使用线程池、任务调度和异步编程等技术可以提高并发性能和资源利用率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值