【C#】并行编程实战:任务并行性(中)

这篇具有很好参考价值的文章主要介绍了【C#】并行编程实战:任务并行性(中)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        本教程对应学习工程:魔术师Dix / HandsOnParallelProgramming · GitCode        

        本章继续介绍任务并行性,因篇幅所限,本章为中篇。


4、取消任务

        .NET Framework 提供了以下两个类来支持任务取消:

  • CancellationTokenSource :此类负责创建取消令牌,并将取消请求传递给通过源创建的所有令牌。

  • CancellationToken:侦听器使用该类来监视请求的当前状态。

        其实和 2.8 的内容有点类似,接下来按照教程步骤走一遍:

CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;       

4.2、使用令牌创建任务

        创建任务的API就有很多了,可用 2.1 ~ 2.3 的任意一种:

var task = new Task(TestFunction.LoopFuntion, token);
var task = Task.Factory.StartNew(TestFunction.LoopFuntion, token);
var task = Task.Run(TestFunction.LoopFuntion, token);

        我设定的 LoopFuntion 是个无参函数,那么这个 token 有什么用呢?

        我一开始想的是,我运行了一个 Task,并传入了 token,他在后台长时间运行。当我需要取消任务时,则调用 CancellationTokenSource 的 Cancel 方法,我创建的 Task 就取消了 。但实际并不是这样。取消令牌只能在任务开始前取消任务,而任务一旦开始运行,则无法取消任务!

        其实和上一章说的 BackgroundWorker 类似,一旦开始执行就取消不了了,即便对 Task 进行 Dispose 也不行。需要程序员在函数中自行实现取消的方法,将方法修改如下:

        var task = Task.Run(() => TestFunction.LoopFuntion(token), token);
        
        public static async void LoopFuntion(CancellationToken token)
        {
            int index = 0;

            for (int i = 0; i < 10; i++)
            {
                await Task.Delay(1000);
                index++;
                Debug.Log($"LoopFuncion ,Number : {index}");

                if (token.IsCancellationRequested)
                {
                    Debug.Log("手动取消!");
                    return;//手动取消;
                }
            }
        }

4.3、注册请求取消的回调

        CancellationToken 中可以注册一个回调函数,在取消时触发。同样在上述的LoopFunction中,可以如下写代码:

//注册一个事件,在 token 设置为取消时触发中断循环
bool IsCancelled = false;
token.Register(() =>
{
    IsCancelled = true;
});

5、等待正在运行的任务

        TPL 中提供了多种用于等待一个或多个任务的 API,具体如下所示:

5.1、Task.Wait

        我们先写一个简单示例如下:

        private void RunWithTaskWait()
        {
            Debug.Log("RunWithTaskWait Start !");
            var task = Task.Run(TestFunction.DebugWithTaskDelay);
            Debug.Log("子线程已经开始运行 !");
            task.Wait();
            Debug.Log("RunWithTaskWait End!");
        }

        我在Unity主线程中调用,猜猜结果会如何?可能你会认为最后一条Log(“RunWithTaskWait End!”)会在 task 完全执行完成之后才会打印,然而其实并不会:

【C#】并行编程实战:任务并行性(中)

         这个效果和没有 Wait 的效果是一样的。

        这个和书上说的就不一样了,无语……我在想作者写书的时候是不是根本没有运行过啊……总之我们先不纠结这个,先看看怎么实现 Task 的等待:

        RunWithTaskWait 不变,然后 DebugWithTaskDelay 做如下修改:

        public static async Task DebugWithTaskDelay()
        {
            Debug.Log("TaskDelay Start");
            await Task.Delay(2000);//等待2s
            Debug.Log("TaskDelay End");
        }

        发现区别没有?我们把返回值从 void 直接改成 Task,没有编译错误,而且运行结果也正确了。(我这样写,在等待的时候会阻塞 Unity 主线程,大家可以在另一个 Task 调用 DebugWithTaskDelay)

【C#】并行编程实战:任务并行性(中)

         其实从C#的源代码上可以看到,Task.Run 有以下两个重载:

public static Task Run(Func<Task> function);//函数在 Task 调度中执行
public static Task Run(Action action);//函数当成普通任务执行

        调用下面这个 Run 是不会有效果的,类似于普通函数,只有上面那个 Run 才是正确的。所以我们需要返回一个 Task 才能正确执行任务里的逻辑。而 Task 又比较特殊,和 void 一样不用 return !这个确实是我没有想到的,真有你的 C# !不得不说,我对 Task 的理解又更进一步 ~

5.2、Task.WaitAll

        Task.WaitAll 用于等待多个任务,任务将作为数组的传递给方法,并且调用程序将被阻塞,直至所有任务都完成。Task.WaitAll 还支持超时和取消令牌。

        在 TestFunction 中添加一个传参的等待方法:

        /// <summary>
        /// 传参的等待方法;
        /// </summary>
        /// <param name="millisecondsDelay">毫秒</param>
        /// <returns></returns>
        public static async Task DebugWithTaskWaitByParameter(int millisecondsDelay)
        {
            Debug.Log($"开始等待:{millisecondsDelay} !");
            await Task.Delay(millisecondsDelay);
            Debug.Log($"结束等待:{millisecondsDelay} !");
        }

        之后我们尝试执行 Task.WallAll,这里我为了避免卡死主线程,所以开了一个新线程来执行:

        private void RunWithTaskWaitAll()
        {
            Thread thread = new Thread(() =>
                {
                    Debug.Log($"开始 RunWithTaskWaitAll!");
                    var task1 = Task.Run(() => TestFunction.DebugWithTaskWaitByParameter(1000));
                    var task2 = Task.Run(() => TestFunction.DebugWithTaskWaitByParameter(2000));
                    var task3 = Task.Run(() => TestFunction.DebugWithTaskWaitByParameter(3000));
                    Task.WaitAll(task1, task2, task3);
                    Debug.Log($"完成 RunWithTaskWaitAll!");
                });
            thread.Start();
        }

        效果如下:

【C#】并行编程实战:任务并行性(中)

        和预期效果一样。

5.3、Task.WaitAny

        Task.WaitAny 也可以等待多个任务,顾名思义,只要等待的任务中有任何一个执行完毕,调用线程就不会阻塞。 Task.Wait、Task.WaitAll 和 Task.WaitAny 都可以设置超时时间和取消令牌,这个就是纯 API 的调用,没有什么变化,就不再赘述了。

        Task.WaitAny 的测试代码如下:

        private void RunWithTaskWaitAny()
        {
            Thread thread = new Thread(() =>
            {
                Debug.Log($"开始 RunWithTaskWaitAny!");
                var task1 = Task.Run(() => TestFunction.DebugWithTaskWaitByParameter(1000));
                var task2 = Task.Run(() => TestFunction.DebugWithTaskWaitByParameter(2000));
                var task3 = Task.Run(() => TestFunction.DebugWithTaskWaitByParameter(3000));
                Task.WaitAny(task1, task2, task3);
                Debug.Log($"完成 RunWithTaskWaitAny!");
            });
            thread.Start();
        }

        结果如下:

【C#】并行编程实战:任务并行性(中)

5.4、Task.WhenAll

        Task.WhenAll 是 Task.WatiAll 方法的非阻塞变体,区别在于会返回一个 Task ,代表所有指定任务的等待。单看概念可能不好理解,直接上代码:

        private void Update()
        {
            if (RunningTask == null)
                return;

            switch (RunningTask.Status)
            {
                case TaskStatus.RanToCompletion:
                    Debug.Log("RunningTask 运行完成!");
                    RunningTask.Dispose();
                    RunningTask = null;
                    break;
            }
        }

        private Task RunningTask;

        private void RunWithTaskWhenAll()
        {
            Thread thread = new Thread(() =>
            {
                Debug.Log($"开始 RunWithTaskWhenAll!");
                var task1 = Task.Run(() => TestFunction.DebugWithTaskWaitByParameter(1000));
                var task2 = Task.Run(() => TestFunction.DebugWithTaskWaitByParameter(2000));
                var task3 = Task.Run(() => TestFunction.DebugWithTaskWaitByParameter(3000));
                RunningTask = Task.WhenAll(task1, task2, task3);
                Debug.Log($"完成 RunWithTaskWhenAll!");
            });
            thread.Start();
        }

        这里开了一个 Update 来轮询 Task.WhenAll 返回的 Task 的结果,当 RunningTask 完成时进行一次打印。其余部分都是把 5.2 的 Task.WaitAll 测试用例抄过来的。

        结果如下:

【C#】并行编程实战:任务并行性(中)

         结果很明显,开始执行3个等待任务的时候,并没有阻塞调用线程。而等到3个任务都完成之后,RunningTask 才会标记为完成。

5.5、Task.WhenAny

        这个很显然了,就是 WaitAny 的非阻塞变体。WaitAny 和 WhenAny 的区别就和 WaitAll 和 WhenAll 的区别一样,就是功能的排列组合,这里就不再赘述了。

6、处理任务异常

        所有优秀的程序员都擅长高效地处理异常,这也是并行编程最重要的方面之一。任务并行库(TPL)提供了一种高效的设计来处理异常:任务中发生的任何未处理异常都将被延迟,然后传播到使用 Join 方法加入的线程,后者负责观察任务中的异常。

        下面我们通过代码实例来学习:

6.1、处理来自单个任务的异常

        首先,我们需要写一个会出异常的程序:

        /// <summary>
        /// 一个“可能”错误的程序;
        /// 会抛出异常错误
        /// </summary>
        public async static Task ErrorFunction()
        {
            var random = new System.Random();
            int div = random.Next(-2, 2);
            float ret = 1;
            for (int i = 0; i < 10; i++)
            {
                if (div == 0)
                {
                    //这里我们只打印,但是并不中断运行;
                    Debug.LogError("开始除0了!");
                }

                //直接除法,抛出除0的移除
                ret += i / div;
                await Task.Yield();
                div = random.Next(-2, 2);
            }
            Debug.Log($"ErrorFunction 居然成功完成了!结果为:{ret} | {div}");
        }

        之后我们直接运行这段程序,就按照最简单的 Task.Run 来运行。结果很有意思啊:

【C#】并行编程实战:任务并行性(中)

         发现没有,已经出现除0的警告了,但是并没有跑错误出来,Unity 一点反应没有!这说明在子线程里的异常是不会直接抛给主线程的。

        下面我们换一个写法:

        private void RunWithErrorTask()
        {
            try
            {
                Debug.Log("RunWithErrorTask 开始!");
                var task=Task.Run(TestFunction.ErrorFunction);
                task.Wait();//不用 task.Wait() 则不会抛出异常
            }
            catch (System.Exception ex)
            {
                Debug.LogError(ex.Message);
                Debug.LogError(ex.StackTrace);
                Debug.LogError(ex.InnerException);
            }
        }

        我们调用 task.Wait(),用 try catch 语句进行包裹,结果如下:

【C#】并行编程实战:任务并行性(中)

【C#】并行编程实战:任务并行性(中)

         其实没啥好说的,就是因为 task.Wait 调回了主线程,所以能接收到异常。上面2张截图,其实就是为了说明 Exception 的 StackTrace 和 InnerException 的区别:可以看到 StackTrace 是没有行号的,但是 InnerException 是可以定位到具体的方法。

6.2、处理来自多个任务的异常

        类似于 5.3 那种,子任务有多个的情况,异常处理也类似。把 catch 的类型换成 AggregateException 就能拿到所有的异常了。

        这里就不贴代码了,只要一张贴图就能明白所有:

【C#】并行编程实战:任务并行性(中)

6.3、使用回调函数处理任务异常

        这里指的就是 AggregateException 运行使用回调来处理异常:

            .......
            catch (System.AggregateException ex)
            {
                ex.Handle(exception =>
                {
                    Debug.LogError(exception.InnerException);
                    return true;
                });
            }

        这里就是 Handle 提供一个方法,返回 true 表示此异常已经正确处理,返回 false 则系统会再次抛出此异常。

        这些都是通用的 C# 函数异常处理方法了,就不必要再多说了。

AggregateException.Handle(FuncInvokes a handler on each Exception contained by this AggregateException.https://learn.microsoft.com/en-us/dotnet/api/system.aggregateexception.handle?view=netstandard-2.1


            本文介绍了取消任务、等待任务、任务异常收集的基本写法、语法介绍等。这些都是平时进行 TPL 编程时常用的 API ,我只是简单介绍一下。后续熟练使用还是要多多练习。文章来源地址https://www.toymoban.com/news/detail-487121.html

到了这里,关于【C#】并行编程实战:任务并行性(中)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C# 任务并行类库Parallel调用示例

    Task Parallel Library 是微软.NET框架基础类库(BCL)中的一个,主要目的是为了简化并行编程,可以实现在不同的处理器上并行处理不同任务,以提升运行效率。Parallel常用的方法有For/ForEach/Invoke三个静态方法。

    2024年02月04日
    浏览(33)
  • C# 消息队列、多线程、回滚、并行编程、异步编程、反射

    消息队列是一种在应用程序之间传递消息的异步通信机制。它可以使应用程序解耦并提高系统的可伸缩性和可靠性。在 C# 中,你可以使用多个消息队列技术,其中一种广泛使用的技术是 RabbitMQ。 RabbitMQ 是一个开源的消息代理,实现了高级消息队列协议(AMQP),提供了强大的

    2024年01月17日
    浏览(42)
  • THRUST:一个开源的、面向异构系统的并行编程语言:编程模型主要包括:数据并行性、任务并行性、内存管理、内存访问控制、原子操作、同步机制、错误处理机制、混合编程模型、运行时系统等

    作者:禅与计算机程序设计艺术 https://github.com/NVIDIA/thrust 2021年8月,当代科技巨头Facebook宣布其开发了名为THRUST的高性能计算语言,可用于在设备、集群和云环境中进行并行计算。它具有“易于学习”、“简单易用”等特征,正在逐步取代C++、CUDA、OpenCL等传统编程模型,成为

    2024年02月07日
    浏览(50)
  • 大数据学习(18)-任务并行度优化

    大数据学习 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍+收藏⭐️+留言📝支持一下博主哦🤞 对于一个分布式的计算任务而言,设置一个合适的并行度十分重要。Hive的计算任务由MapReduce完成,故并行度的

    2024年02月07日
    浏览(64)
  • 基于C#编程建立泛型Matrix数据类型及对应处理方法

            上一篇文档中描述了如何写一个VectorT类,本次在上一篇文档基础上,撰写本文,介绍如何书写一个泛型Matrix,可以应用于int、double、float等C#数值型的matrix。         本文所描述的MatrixT是一个泛型,具有不同数值类型Matrix矩阵构造、新增、删除、查询、更改、

    2024年02月02日
    浏览(43)
  • 【深度学习】YOLOv8训练过程,YOLOv8实战教程,目标检测任务SOTA,关键点回归

    https://github.com/ultralytics/ultralytics 官方教程:https://docs.ultralytics.com/modes/train/ 更建议下载代码后使用 下面指令安装,这样可以更改源码,如果不需要更改源码就直接pip install ultralytics也是可以的。 这样安装后,可以直接修改yolov8源码,并且可以立即生效。此图是命令解释: 安

    2024年02月10日
    浏览(61)
  • Django实战项目-学习任务系统-任务管理

    接着上期代码框架,开发第3个功能,任务管理,再增加一个学习任务表,用来记录发布的学习任务的标题和内容,预计完成天数,奖励积分和任务状态等信息。 第一步:编写第三个功能-任务管理 1,编辑模型文件: ./mysite/study_system/models.py: 2,编辑urls配置文件: ./mysite/stu

    2024年02月06日
    浏览(48)
  • 【c#】Quartz开源任务调度框架学习及练习Demo

    Quartz是一个开源的任务调度框架,作用是支持开发人员可以定时处理业务,比如定时发布邮件等定时操作。 Quartz大致可以分为四部分,但是按功能分的话三部分就可以:schedule(调度器是schedule的一个调度单元)、job(任务)、Trigger(触发器) scedule功能:统筹任务调度, JOB:实现

    2024年02月08日
    浏览(34)
  • Django实战项目-学习任务系统-定时任务管理

    接着上期代码框架,开发第4个功能,定时任务管理,再增加一个学习定时任务表,主要用来设置周期重复性的学习任务,定时周期,定时时间,任务标题和内容,预计完成天数,奖励积分和任务状态等信息。 现实中学习一门课程或一项技能知识,需要很长时间的学习积累才

    2024年02月08日
    浏览(43)
  • SpringBoot异步任务及并行事务实现

            上一篇介绍了原生Java如何实现串行/并行任务,主要使用了线程池 + Future + CountDownLatch,让主线程等待子线程返回后再向下进行。而在SpringBoot中,利用@Async和AOP对异步任务提供了更加便捷的支持,下面就针对SpringBoot使用异步任务需要注意的细节做一些分析。      

    2024年02月02日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包