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

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

        在 .NET 的初始版本中,我们只能依赖线程(线程可以直接创建或者使用 ThreadPool 类创建)。ThreadPool 类提供了一个托管抽象层,但是开发人员仍然需要依靠 Thread 类来进行更好的控制。而 Thread 类维护困难,且不可托管,给内存和 CPU 带来沉重负担。

        因此,我们需要一种方案,既能充分利用 Thread 类的优点,又规避它的困难。这就是任务 (Task)。

        (另:本章篇幅较大,将分为上种下三部分发表。)


1、任务(Task)的特性

        任务(Task)是 .NET 中的抽象,一个异步单位。从技术上讲,任务不过是对线程的包装,并且这个线程还是通过 ThreadPool 创建的。但是任务提供了诸如等待、取消和继续之类的特性,这些特性可以在任务完成后运行。

        任务具有以下重要特性:

  • 任务由 TaskScheduler (任务调度程序)执行,默认的调度仅在 ThreadPool 上运行。

  • 可以从任务中返回值。

  • 任务在完成时有通知(ThreadPool 和 Thread 都没有)。

  • 可以使用 ContinueWith() 构造连续执行的任务。

  • 可以通过调用 Task.Wait() 等待任务的执行,这将阻塞调用线程,直到任务完成为止。

  • 与传统线程或 ThreadPool 相比,任务可以使代码的可读性更高。他们还为在 C# 5.0 中引入异步编程构造铺平了道路。

  • 当一个任务从另一个任务启动时,可以建立它们之间的父子级关系。

  • 可以将子任务的异常传播到父任务。

  • 可以使用 CancellationToken 类取消任务。

2、创建和启动任务

        我们可以通过多种方式使用任务并行库(TPL)创建和运行任务。

2.1、使用 Task

        Task 类是作为 ThreadPool 线程异步执行工作的一种方式。它采用的是基于任务的异步模式( Task-Based Asynchronous Pattern,TAP)。非通用 Task 类不会返回结果,因此当需要从任务中返回值时,就需要使用通用版本的 Task<T> 。Task 需要调用 Start 方法来调度运行。

        具体的 Task 调用代码如下:

        /// <summary>
        /// 测试方法,打印10次,等待10秒
        /// </summary>
        public static void DebugAndWait()
        {
            int length = 10;
            for (int i = 0; i < length; i++)
            {
                Debug.Log($"执行第:{i + 1}/{length} 次打印!");
                Thread.Sleep(1000);
            }
        }
        
        //使用任务执行
        private void RunByNewTask()
        {
            //创建任务
            Task task = new Task(TestFunction.DebugAndWait);
            task.Start();//不调用 Start 则不会执行
        }

        最终结果也没有什么意外:

2.2、使用 Task.Factory.StartNew

        TaskFactory 类的 StartNew 方法也可以创建任务。这种方式创建的任务将安排在 ThreadPool 中执行,然后返回该任务的引用:

        private void RunByTaskFactory()
        {
            //使用 Task.Factory 创建任务,不需要调用 Start
            var task = Task.Factory.StartNew(TestFunction.DebugAndWait);
        }

        当然打印的结果和上述一样的。

2.3、使用 Task.Run

        这个原理和 Task.Factory.StartNew 一样:

        private void RunByTaskRun()
        {
            //使用 Task.Run 创建任务,不需要调用 Start
            var task = Task.Run(TestFunction.DebugAndWait);
        }

2.4、Task.Delay

        使用 Task.Delay 也可以创建一个任务,但是这个任务有点特别。它可以在指定时间间隔后完成,可以使用

        CacellationToken 类随时取消。与 Thread.Sleep 不同,Task.Delay 不需要利用 CPU 周期,且可以异步运行。

        为了体现两者的不同,我们直接写个例子:

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

        然后我们直接在程序中直接同步调用此方法:

        private void RunWithTaskDelay()
        {
            Debug.Log("开始测试 Task.Delay !");
            TestFunction.DebugWithTaskDelay();
            Debug.Log("结束测试 Task.Delay !");
        }

        结果如下:

         可以看到4条打印按照顺序一瞬间被打印出来了,根本没有任何等待。而如果我们把上述的 Task.Delay 替换成 Thread.Sleep,结果会如何呢?

         在运行此方法后,Unity直接卡住,然后2s后打印出4条信息。并且,显然线程等待生效了,但是是以阻塞主线程的方式生效的。

        让我们换回 Task.Delay ,并使用 Task.Run 来运行这个方法,打印结果如下:

         显然线程等待命令生效了,说明在子线程中的 Delay 是可以正常工作的。

2.5、Task.Yield

        Task.Yiled 是创建 await 任务的另一种方法。使用此方法可以让方法强制变成异步的,并将控制权返回给操作系统。

        怎么理解呢?我们这里需要一个很耗时的函数:

        public static async void DebugWithTaskYield()
        {
            int length = 27;//这个方法不能执行很多次
            string str = "";

            for (int i = 0; i < length; i++)
            {
                //以下是耗时函数
                str += "1,1";
                var arr = str.Split(',');
                foreach (var item in arr)
                {
                    str += item;
                }

                await Task.Yield();
                Debug.Log($"执行第:{i + 1}/{length} 次打印!");
            }
        }

        这里我直接用简单的字符串拼接来实现了耗时函数。

        我们在主线程调用 Task.Run 来执行,Debug 的结果如下:

         可以看到随着字符串的增加,单次耗时越来越长。但是无论单次耗时时长有多少,都没有阻碍主线程!可能大家第一感觉和 Unity 的协程是一样的,但是 Unity 的协程使用是在主线程运行的,使用协程并不代表不会阻塞主线程。这里我们直接将这段代码用协程的逻辑实现:

        public static IEnumerator DebugWithCoroutine()
        {
            int length = 27;//这个方法不能执行很多次
            string str = "";

            for (int i = 0; i < length; i++)
            {
                //以下是耗时函数
                str += "1,1";
                var arr = str.Split(',');
                foreach (var item in arr)
                {
                    str += item;
                }

                yield return null;
                Debug.Log($"执行第:{i + 1}/{length} 次打印!");
            }
        }

        逻辑上没有任何区别,就是把 await Task.Yield(); 改成了 yield return null 。当然,日志打印上看起来差不多,但是对主线程而言有本质区别。当运行到后面时,每次迭代都会造成主线程的卡顿。这一点在 Profiler 上看起来非常明显:

 (可以看到协程调用的显然耗时)

2.6、Task.FromResult

        FromResult<T> 是在 .NET Framework 4.5 中才被引入的方法,这在 Unity 2022.2.5 f1c1 使用的 .NET Standard 2.1 是支持的。

        public static int FromResultTest()
        {
            int length = 100;
            int result = 0;
            for (int i = 0; i < length; i++)
                result += Random.Range(0, 100);
            Debug.Log($"FromResultTest 运算结果:{result} ");
            return result;
        }
        
        private void RunWithFromResult()
        {
            Debug.Log("RunWithFromResult Start !");
            Task<int> resultTask = Task.FromResult<int>(TestFunction.FromResultTest());
            Debug.Log("RunWithFromResult End ! Result : " + resultTask.Result);
        }

        如上述代码所示 RunWithFromResult 的结果如下:

         与一般的Task异步不同,这里是按照执行顺序依次打印的。如果这个函数是个耗时函数,会阻塞主线程吗?我把 2.5 里测试的耗时函数搬过来测试了一下(就不贴代码了):

         显然已经阻塞主线程了。

        也就是说这个 FromResult 将异步的方法拿到主线程中调度了(也可以理解为把子线程直接拿到父线程)。既然已经是 Unity 主线程了,那么 Task.Delay 就不会生效;而 Thread.Sleep 会生效,且会阻塞主线程。

        与前面的几个创建Task任务的方法不同,这个Task.FromResult 是可以调用带参函数的(Task.Run 只能运行无参函数)。但即便如此,因为其会阻塞父线程,也不建议在 Unity 主线程中使用。

2.7、Task.FromException 和 Task.FromException<T>

        这两个方法都可以抛出异步任务中的异常,在单元测试中很有用。

        (这里暂时不会用到,就先不讲了,在后面学单元测试的时候再详细学习这两个)

2.8、Task.FromCanceled 和 Task.FromCanceled<T>

                这个和 Task.FromException 的情况有点类似,都是看起来不知道有啥用其实很有用的方法。为了方便学习,这里还是展开讲讲。

        首先看下面一段代码,这个也是 Task.FromCanceled 的示例代码:

CancellationTokenSource source = new CancellationTokenSource();//构建取消令牌源
source.Cancel();//设置为取消

//返回标记为取消的任务。
//注意!使用此方法要确保 CancellationTokenSource 已经调用过 Cancel 方法 ,否则会出错!
Task.FromCanceled(source.Token);

        当我们把这个最后得到的Task状态(Task.Status)打印出来,其结果是便是 Created 。

        肯定就有人会问了,这个有啥用啊?我是创建了一个取消的任务?那我执行这段代码的意义是什么呢?

        单看这段代码,确实没什么意义,但是我们这里提出一个需求:

         逻辑很简单,但是问题就出在最后,要维护一个Task。我们假设预计执行的任务A是某个长期的异步函数,外部需要检测他的状态和结果。那我们在输入偶数的时候,该返回什么呢?首先肯定不能返回一个空的Task,这个返回就和正常的Task一样的了,外部监控的状态要么是 WaitingToRun, 要么就是 RanToCompletion,要么就是 Running 。我根本无法知道我是执行了 任务A 还是没有执行 任务A。

        这时候就发现 Task.FromCanceled 的作用了:

        private void RunWithFromCanceled()
        {
            var val = commonPanel.GetInt32Parameter();
            //这里测试输入双数就取消执行,单数就正常执行。
            CancellationTokenSource source = new CancellationTokenSource();
            if (val % 2 == 0)
                source.Cancel();
            var task = TestFunction.TestCanceledTask(source);
            Debug.Log($"Task State 1: {task.Status}");
        }
        
        /// <summary>
        /// 测试用于取消任务
        /// </summary>
        public static Task TestCanceledTask(CancellationTokenSource source)
        {
            if (source.IsCancellationRequested)
            {
                Debug.Log($"任务取消 !");
                var token = source.Token;       
                return Task.FromCanceled(token);
            }
            else
            {
                Debug.Log($"任务执行 !");
                return Task.Run(DebugWithTaskDelay);
            }
        }

        当输入偶数时,就会返回一个已取消的任务,而奇数则会正常执行。

        当我们对任务进行了封装,内部的判断逻辑会比较复杂,而外部也只需要知道任务执行情况而不需要知道其内部逻辑。此时使用 Task.FromCanceled 和 Task.FromException 就能返回给外部一个通用的“异常”Task。

3、从完成的任务中获取结果

        任务并行库(TPL)中提供的API有如下几个:

        /// <summary>
        /// 获取任务并行结果
        /// </summary>
        private void GetTaskResult()
        {
            int inputParam = commonPanel.GetInt32Parameter();
            Debug.Log($"get task result start ! paramter :  {inputParam}");

            //方法1 :new Task
            var task_1 = new Task<int>(()=>TestFunction.FromResultTest(inputParam));
            task_1.Start();
            Debug.Log($"task_1 result : {task_1.Result}");

            //方法2:Task.Factory
            var task_2 = Task.Factory.StartNew<int>(()=> TestFunction.FromResultTest(inputParam));
            Debug.Log($"task_2 result : {task_2.Result}");

            //方法3:
            var task_3 = Task.Run<int>(()=>TestFunction.FromResultTest(inputParam));
            Debug.Log($"task_3 result : {task_3.Result}");

            //方法4:
            var task_4 = Task.FromResult<int>(TestFunction.FromResultTest(inputParam));
            Debug.Log($"task_4 result : {task_4.Result}");
        }

        这次测试终于出现了一个熟悉的错误:

         Random.Range 只能在Unity主线程使用。

        这个以前就知道 UnityEngine 的类不能在子线程使用,这里遇到了。但是没关系,我们直接修改这个方法即可,用System的Random就行了。

        但是这能说明我们的程序确实在子线程运行了,但是实际上这4个方法都是会阻塞主线程的

         所有的运算流程都是和 2.6 的 FromResult 一样,已经将子线程调回主线程使用了。显然这几种方法都是提供一种同步的结果获取,而真正做到异步计算还不能直接这么使用。


        限于篇幅,任务并行性(上)到此为止。

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
清晰完整PDF版本,是我从网上买来的 共 60MB,分为11个分卷 在 CSDN 上只有我整个是清晰完整的 LINQ 实战 1/11 LINQ 实战 .NET 2010 SQL 出版社:人民邮电出版社 ISBN:9787115208514 原价:65元 作者:(法)马格里,(美)艾歇特,(美)伍利 著,陈黎夫 译 出版日期:2009-8-1 字数:668000 页码:416 内容提要 -------------------------------------------------------------------------------- 作为.NET上连接编程语言和数据库、内存对象、XML等各种类型数据之间的桥梁,LINQ引入了一种处理数据的全新理念,将查询无缝集成至开发语言之上。本书第一部分介绍了LINQ技术及C#和VB为此提供的新语言特性,第二、三、四部分详细介绍了LINQ的三大主要功能LINQ to Objects、LINQ to SQL、LINQ to XML,第五部分研究了LINQ的可扩展性、在应用程序中使用各种LINQ功能的方法以及LINQ to DataSet。   本书适合于使用C#和VB.NET的软件开发者。 编辑推荐 -------------------------------------------------------------------------------- 微软MVP呕心沥血之作,LINQ圣经,深入全面,引人入胜,丰富实例,兼顾C#和VB.NET语言。   本书作者是LINQ社区中的重要成员,他们在书中展示了这一技术的精髓……快去享受这一盛宴吧。          ——Matt Warren,微软主架构师,LINQ之父   LINQ方面的杰作!深入、全面、客观,令人欲罢不能。强烈推荐!         ——Patrick Smacchia,微软MVP,《C#和.NET 2.0 实战》作者 目录 -------------------------------------------------------------------------------- 第一部分 从这里开始 第1章 LINQ简介 2 1.1 LINQ是什么 2 1.1.1 概览 3 1.1.2 LINQ的功能 3 1.1.3 作为语言扩展的LINQ 4 1.2 为什么需要LINQ 6 1.2.1 常见问题 6 1.2.2 解决理念上的失调 8 1.2.3 LINQ给出解决方案 12 1.3 LINQ的设计目标及起源 13 1.3.1 LINQ项目的设计目标 13 1.3.2 一点历史 14 1.4 LINQ to Objects初步:查询内存中的集合 15 1.4.1 先决条件 15 1.4.2 Hello LINQ to Objects 17 1.5 LINQ to XML初步:查询XML文档 20 1.5.1 为什么需要LINQ to XML 20 1.5.2 Hello LINQ to XML 22 1.6 LINQ to SQL初步:查询关系型数据库 26 1.6.1 LINQ to SQL功能概览 26 1.6.2 Hello LINQ to SQL 26 1.6.3 进一步了解LINQ to SQL 30 1.7 小结 30 第2章 C#和VB语言的增强特性 31 2.1 新语言的增强特性一瞥 31 2.1.1 列出当前正在运行的所有进程 32 2.1.2 将返回结果封装成类 33 2.2 隐式类型局部变量 34 2.2.1 语法 35 2.2.2 使用隐式类型局部变量改进实例程序 35 2.3 对象和集合的初始化器 37 2.3.1 引入对象初始化器的动机 37 2.3.2 集合初始化器 38 2.3.3 用对象初始化器改进实例程序 39 2.4 Lambda表达式 40 2.4.1 委托回顾 40 2.4.2 匿名方法 42 2.4.3 Lambda表达式介绍 42 2.5 扩展方法 47 2.5.1 创建扩展方法的示例 47 2.5.2 更多使用LINQ标准查询操作符的例子 50 2.5.3 将扩展方法引入到实例程序中 51 2.5.4 注意事项 52 2.6 匿名类型 54 2.6.1 使用匿名类型组合数据 55 2.6.2 没有名字的类型也是类型 55 2.6.3 使用匿名类型改进实例程序 56 2.6.4 匿名类型的限制 57 2.7 小结 58 第3章 LINQ构建块 61 3.1 LINQ对.NET的扩展 61 3.1.1 语言扩展回顾 61 3.1.2 构成LINQ基础的核心元素 62 3.2 序列 63 3.2.1 IEnumerableT接口 63 3.2.2 迭代器回顾 64 3.2.3 延迟查询执行 66 3.3 查询操作符 69 3.3.1 查询操作符是什么 69 3.3.2 标准查询操作符 71 3.4 查询表达式 72 3.4.1 查询表达式是什么 72 3.4.2 编写查询表达式 73 3.4.3 标准查询操作符与查询表达式的关系 75 3.4.4 限制 76 3.5 表达式树 78 3.5.1 Lambda表达式回顾 78 3.5.2 表达式树是什么 78 3.5.3 IQueryable,另一种实现延迟查询执行的方法 81 3.6 LINQ的程序集以及命名空间 81 3.7 小结 83 第二部分 查询内存中的对象 第4章 熟悉LINQ to Objects 86 4.1 实例程序简介 86 4.1.1 程序目标 86 4.1.2 程序功能 87 4.1.3 业务逻辑实体 87 4.1.4 数据库架构 87 4.1.5 示例数据 89 4.2 用LINQ操作内存中的集合 90 4.2.1 能够查询什么 90 4.2.2 支持的操作 94 4.3 在ASP.NET和Windows Forms中使用LINQ 95 4.3.1 Web应用程序中的数据绑定 95 4.3.2 Windows Forms应用程序中的数据绑定 100 4.4 常用的标准查询操作符 103 4.4.1 约束操作符Where 104 4.4.2 投影操作符 105 4.4.3 Distinct操作符 107 4.4.4 转换操作符 108 4.4.5 聚合操作符 109 4.5 用不同的方式显示内存中的集合 110 4.5.1 排序 110 4.5.2 嵌套查询 111 4.5.3 分组 113 4.5.4 使用连接 114 4.5.5 分区 117 4.6 小结 121 第5章 超越基本的内存数据查询 122 5.1 常见使用场景 122 5.1.1 查询非泛型集合 123 5.1.2 按照多个条件分组 125 5.1.3 动态查询 127 5.1.4 LINQ to Text Files 135 5.2 设计模式 137 5.2.1 函数式创建模式 138 5.2.2 ForEach模式 140 5.3 性能分析 142 5.3.1 选择恰当的流操作方式 142 5.3.2 当心立即执行 144 5.3.3 LINQ to Objects会降低代码的性能吗 146 5.3.4 使用LINQ to Objects的代价 149 5.3.5 性能和简洁:鱼和熊掌不可兼得吗 151 5.4 小结 152 第三部分 查询关系型数据 第6章 LINQ to SQL入门 156 6.1 走入LINQ to SQL 157 6.1.1 创建对象映射 159 6.1.2 设定DataContext 161 6.2 通过LINQ to SQL读取数据 161 6.3 继续改进查询 165 6.3.1 过滤 165 6.3.2 排序和分组 167 6.3.3 聚集 168 6.3.4 连接 169 6.4 操作有层级关系的对象 171 6.5 数据加载的时机以及为何要关心这些 174 6.5.1 延迟加载 174 6.5.2 立即加载详细信息 175 6.6 更新数据 177 6.7 小结 179 第7章 揭开LINQ to SQL的本质 180 7.1 对象与关系数据的映射 180 7.1.1 通过在类中添加属性 181 7.1.2 使用外部XML文件实现映射 185 7.1.3 使用SqlMetal工具 186 7.1.4 LINQ to SQL设计器 188 7.2 将查询表达式转换为SQL 190 7.2.1 IQueryable接口 190 7.2.2 表达式树 191 7.3 业务实体的生命周期 193 7.3.1 跟踪变化 195 7.3.2 提交变化 196 7.3.3 操作离线数据 198 7.4 小结 200 第8章 LINQ to SQL高级特性 201 8.1 处理并发修改 201 8.1.1 悲观式并发 201 8.1.2 乐观式并发 202 8.1.3 处理并发异常 204 8.1.4 使用事务解决冲突 207 8.2 高级的数据库功能 208 8.2.1 使用SQL语句直接得到结果对象 209 8.2.2 使用存储过程 210 8.2.3 用户自定义函数 217 8.3 改进业务逻辑层 220 8.3.1 预编译的查询 220 8.3.2 使用部分类实现自定义业务逻辑 221 8.3.3 使用部分方法 223 8.3.4 使用对象继承 225 8.4 LINQ to Entities简介 229 8.5 小结 231 第四部分 操作XML 第9章 LINQ to XML入门 234 9.1 什么是XML API 234 9.2 为什么需要另外一种XML编程API 235 9.3 LINQ to XML设计原则 236 9.3.1 核心概念:函数式创建 238 9.3.2 核心概念:上下文无关的XML创建过程 238 9.3.3 核心概念:简化了的名称 239 9.4 LINQ to XML中相关类的继承体系 241 9.5 在LINQ中操作XML 243 9.5.1 加载XML 243 9.5.2 解析XML 245 9.5.3 创建XML 246 9.5.4 使用Visual Basic XML字面量创建XML 249 9.5.5 创建XML文档 251 9.5.6 向XML中添加内容 254 9.5.7 从XML中移除内容 255 9.5.8 更新XML的内容 256 9.5.9 操作属性 258 9.5.10 保存XML 258 9.6 小结 259 第10章 使用LINQ to XML查询并转换XML 260 10.1 LINQ to XML轴方法 261 10.1.1 Element 262 10.1.2 Attribute 263 10.1.3 Elements 264 10.1.4 Descendants 264 10.1.5 Ancestors 267 10.1.6 ElementsAfterSelf、NodesAfterSelf、ElementsBeforeSelf和NodesBeforeSelf方法 268 10.1.7 Visual Basic的XML轴属性 269 10.2 标准查询操作符 271 10.2.1 使用Select进行投影 273 10.2.2 使用Where实现过滤 274 10.2.3 排序和分组 275 10.3 使用XPath查询LINQ to XML对象 278 10.4 转换XML 279 10.4.1 LINQ to XML转换 279 10.4.2 使用XSLT转换LINQ to XML对象 282 10.5 小结 284 第11章 LINQ to XML常见场景 285 11.1 根据XML构建对象 285 11.1.1 目标 286 11.1.2 实现 287 11.2 根据对象创建XML 290 11.2.1 目标 290 11.2.2 实现 291 11.3 根据数据库中的数据创建XML 295 11.3.1 目标 296 11.3.2 实现 297 11.4 过滤并混合从数据库以及XML中得到的数据 301 11.4.1 目标 302 11.4.2 实现 302 11.5 读取XML并更新数据库 306 11.5.1 目标 306 11.5.2 实现 307 11.6 将文本文件转换为XML 319 11.6.1 目标 319 11.6.2 实现 320 11.7 小结 322 第五部分 LINQ无处不在 第12章 扩展LINQ 324 12.1 探寻LINQ的扩展机制 324 12.1.1 LINQ各个特性是如何实现的 325 12.1.2 自定义LINQ扩展能够实现什么 326 12.2 创建自定义查询操作符 327 12.2.1 改进标准查询操作符 327 12.2.2 辅助或领域相关的查询操作符 330 12.3 基本查询操作符的自定义实现 334 12.3.1 回顾查询翻译机制 334 12.3.2 查询表达式模式的规范 335 12.3.3 示例1:跟踪标准查询操作符的执行过程 337 12.3.4 限制:查询表达式冲突 338 12.3.5 示例2:非泛型的、领域相关的操作符 340 12.3.6 示例3:非序列的查询操作符 342 12.4 查询Web服务:LINQ to Amazon 343 12.4.1 LINQ to Amazon介绍 344 12.4.2 需求 345 12.4.3 实现 346 12.5 IQueryable和IQueryProvider:LINQ to Amazon高级版本 352 12.5.1 IQueryable和IQueryProvider接口 353 12.5.2 实现 356 12.5.3 究竟发生了什么 357 12.6 小结 358 第13章 应用程序各层中的LINQ 359 13.1 LinqBooks应用程序概览 359 13.1.1 功能 359 13.1.2 程序UI概览 360 13.1.3 数据模型 362 13.2 LINQ to SQL及数据访问层 362 13.2.1 回顾传统的三层架构 362 13.2.2 用专门的数据访问层还是LINQ to SQL 364 13.2.3 LinqBooks中对LINQ to SQL的使用 368 13.3 LINQ to XML的使用 374 13.3.1 从Amazon中导入数据 374 13.3.2 生成RSS 376 13.4 LINQ to DataSet的使用 377 13.5 使用LINQ to Objects 380 13.6 可扩展性 380 13.6.1 自定义查询操作符 380 13.6.2 创建并使用自定义LINQ提供器 381 13.7 展望未来 381 13.7.1 自定义LINQ功能 382 13.7.2 LINQ to XSD:强类型的LINQ to XML 382 13.7.3 PLINQ:支持并行计算的LINQ 383 13.7.4 LINQ to Entities,ADO.NET Entity Framework的LINQ接口 383 13.8 小结 384 第14章 使用LINQ操作DataSet 385 14.1 LINQ to DataSet概览 385 14.2 DataSet回顾 386 14.2.1 DataSet使用场景及功能 386 14.2.2 DataSet是什么 387 14.2.3 Visual Studio 2008和.NET 3.5为支持LINQ to DataSet进行的改进 389 14.3 查询弱类型DataSet 390 14.3.1 为DataSet加载数据 390 14.3.2 不使用LINQ查询DataSet 394 14.3.3 使用LINQ to DataSet查询弱类型DataSet 395 14.4 查询强类型DataSet 399 14.4.1 生成强类型的DataSet 400 14.4.2 为强类型DataSet加载数据 402 14.4.3 使用LINQ to DataSet查询强类型DataSet 404 14.5 将LINQ to DataSet查询结果绑定至UI控件 406 14.5.1 使用CopyToDataTable将LINQto DataSet查询结果复制到DataTable 406 14.5.2 使用AsDataView实现双向数据绑定 408 14.6 在LINQ to DataSet中使用查询操作符 410 14.6.1 用于DataRow的FieldT和SetFieldT操作符 410 14.6.2 集合操作符以及用DataRowComparer比较DataRow 411 14.7 小结 412 附录 标准查询操作符 414 作者介绍 -------------------------------------------------------------------------------- 马格里(Fabrice Marguerie),微软C# MVP,资深.NET软件架构师,LINQ社区专家。
并行编程使用多个处理器或处理器核心同时执行多个任务或子任务的一种编程技术。它能够显著提高程序的执行效率和性能。 基于C语言的并行编程可以通过使用多线程或OpenMP来实现。多线程是一种在同一进程内创建多个线程并行执行的技术。OpenMP是一种基于共享内存的并行编程模型,通过在代码中插入一些特殊的指令来实现并行。 在实际应用中,我可以使用C语言进行并行编程来加速计算密集型任务,例如图像处理、数据分析或数值计算等。通过将任务分解成多个子任务,并且使用多线程或OpenMP来同时执行这些子任务,能够有效利用多核处理器的优势,从而加速程序的运行。 另外,基于C语言的并行编程还可以用于实现并行的服务器和网络程序。例如,在编写网络服务器时,可以使用多线程来并发处理多个请求,提高服务器的并发能力和吞吐量。 然而,并行编程也带来了一些挑战和注意事项。例如,线程之间的数据共享和同步问题需要仔细处理,以避免出现竞争条件和死锁等并发问题。另外,编写并行程序需要考虑到任务的负载均衡和性能瓶颈等问题,以充分发挥并行计算的优势。 总之,基于C语言的并行编程通过利用多线程或OpenMP等技术,能够提高程序的执行效率和性能。在实际应用中,我们可以将其应用于计算密集型任务加速、并行服务器和网络程序等场景中。然而,在编写并行程序时需要注意解决线程同步和数据共享等问题,以充分发挥并行计算的优势。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值