在多CPU爱护环境标语大全下,Task任务如何取消

10:37 提问
求助,win10系统task scheduler导致CPU占用100%
Dell笔记本用的insider win10,周末升级了一次。周一开机后task scheduler就开始运行,占用CPU70多,CPU总占用100,风扇一直响.(以前这个服务也运行过)。通过任务管理器,得手动关闭3次,才能把服务关掉,风扇不再响,但是过了一会,它又自己启动了。然后我就想,既然是计划任务,就让它运行一次,晚上没关机。但今天早上来公司,它还在运行。
尝试在服务管理里和QQ管家里禁用这个服务,但都失败。在计算机管理的任务计划程序里找到了task scheduler文件夹,但是内容是空的。
网上说这个服务是有用的,禁用会增加系统不稳定性,但是现在的问题是这个服务在持续不断的运行,一天一夜了,要运行到什么时候呢?
该怎么解决呢,要找办法禁用这个服务吗?还是说是因为某些计划任务导致的,禁掉相关的计划任务就行呢?
按赞数排序
我现在也遇到这个问题,不知道题主解决了吗?可以分享下解决方法吗?
这是微软 “客户体验改善计划” 搞的鬼,解决办法
这个破服务太讨厌了,搞得CPU占用率那么高,真的是无语,禁用以后,过段时间还会自动打开,不知道微软搞得什么系统!还无法降级win7系统,这是要干什么?
准确详细的回答,更有利于被提问者采纳,从而获得C币。复制、灌水、广告等回答会被删除,是时候展现真正的技术了!
其他相关推荐如何关闭W7系统里的taskhost.exe这个应用程序_百度知道
如何关闭W7系统里的taskhost.exe这个应用程序
在任务管理器里面总是自动运行,不是病毒,网上查了好多资料,别说杀毒什么的没用,不是那么回事,求高手明白人给点办法,是一个计划任务的程序,根本不需要 还特别占CPU,CPU能占一半以上,求解决办法,怎么能关闭它,彻底关闭,删除了删除不了,是系统文件,...
答题抽奖
首次认真答题后
即可获得3次抽奖机会,100%中奖。
taskhost.exe进程介绍:  taskhost.exe进程是Windows7自带的一个进程,是负责Windows7运行计划的,简单的来说也可以称作计划任务程序,这个程序也是Windows7的新特色,通过此程序可以定时设置系统中的相关操作,让操作系统到时间就执行您所设置的相关操作非常方便,如果关闭此进程计划的定时任务就会失效.编辑本段taskhost.exe安全分析:  此进程的全称为Host Process for Windows Tasks,存放路径X:\Windows\System32目录下,此进程属于正常的系统进程.   注: 如发现taskhost.exe不在X:\Windows\System32目录下则一定是病毒或木马程序.
采纳率:35%
点开始--在搜索栏打taskschd.msc--点开这个程序--打开任务计划程序库--Microsoft--Windows--双击RAC文件夹
然后右边就会有一个叫RACTask的。
右键属性--触发器--每小时运行一次这个选项
去掉,不要!然后重启.
本回答被网友采纳
taskhost.exe进程介绍:  taskhost.exe进程是Windows7自带的一个进程,是负责Windows7运行计划的,简单的来说也可以称作计划任务程序,这个程序也是Windows7的新特色,通过此程序可以定时设置系统中的相关操作,让操作系统到时间就执行您所设置的相关操作非常方便,如果关闭此进程计划的定时任务就会失效.编辑本段taskhost.exe安全分析:  此进程的全称为Host Process for Windows Tasks,存放路径X:\Windows\System32目录下,此进程属于正常的系统进程.   注: 如发现taskhost.exe不在X:\Windows\System32目录下则一定是病毒或木马程序.   注: X代表系统安装磁盘参考资料:
我也出现相同问题
而且占用很好CPU
求楼主如果有方法也告诉我下 好不 谢谢
其他1条回答
为您推荐:
其他类似问题
w7系统的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。tasklsv.exe占用了很多cpu 这是什么东西_百度知道
tasklsv.exe占用了很多cpu 这是什么东西
答题抽奖
首次认真答题后
即可获得3次抽奖机会,100%中奖。
taskhost.exe进程是Windows7自带的一个进程,是负责Windows7运行计划的,简单的来说也可以称作计划任务程序,这个程序也是Windows7的新特色,通过此程序可以定时设置系统中的相关操作,让操作系统到时间就执行您所设置的相关操作非常方便,如果关闭此进程计划的定时任务就会失效。但是有时候 taskhost.exe进程能够占到一半的cpu使用量,这时我们就需要将其关闭。1、 taskhost.exe存放的路径为X:\Windows\System32目录下,此进程属于正常的系统进程.如发现taskhost.exe不在X:\Windows\System32目录下则一定是病毒或木马程序
为您推荐:
其他类似问题
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。博客分类:
进程cpu资源分配就是指进程的优先权(priority)。优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
一、先看系统进程:
PR 就是 Priority 的简写,而 NI 是 nice 的简写。这两个值决定了PR的值,PR越小,进程优先权就越高,就越“优先执行”。换算公式为:PR(new) = PR(old) + NI
---------------------------------------------------------------------------
二、修改进程优先级的命令主要有两个:nice,renice
1、一开始执行程序就指定nice值:nice
nice -n -5 /usr/local/mysql/bin/mysqld_safe &
linux nice 命令详解
  功能说明:设置优先权。
  语  法:nice [-n &优先等级&][--help][--version][执行指令]
  补充说明:nice指令可以改变程序执行的优先权等级。
  参  数:-n&优先等级&或-&优先等级&或--adjustment=&优先等级&  设置欲执行的指令的优先权等级。等级的范围从-20-19,其中-20最高,19最低,只有系统管理者可以设置负数的等级。
  &&&&&&&&& --help  在线帮助。
 &&&&&&&&&  --version  显示版本信息。
---------------------------------------------------------------------------
2.1、调整已存在进程的nice:renice
renice -5 -p 5200
#PID为5200的进程nice设为-5
linux renice 命令详解
  功能说明:调整优先权。
  语  法:renice [优先等级][-g &程序群组名称&...][-p &程序识别码&...][-u &用户名称&...]
  补充说明:renice指令可重新调整程序执行的优先权等级。预设是以程序识别码指定程序调整其优先权,您亦可以指定程序群组或用户名称调整优先权等级,并修改所有隶属于该程序群组或用户的程序的优先权。等级范围从-20--19,只有系统管理者可以改变其他用户程序的优先权,也仅有系统管理者可以设置负数等级。
  参  数:
  -g &程序群组名称&  使用程序群组名称,修改所有隶属于该程序群组的程序的优先权。
  -p &程序识别码&  改变该程序的优先权等级,此参数为预设值。
  -u &用户名称&  指定用户名称,修改所有隶属于该用户的程序的优先权。
2.2、也可以用top命令更改已存在进程的nice:
top
#进入top后按"r"--&输入进程PID--&输入nice值
---------------------------------------------------------------------------
三、把进程运行到指定CPU(即修改进程的"CPU亲和性"):taskset
&&& 两个名词
&&& SMP (Symmetrical Multi-Processing):指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。 []
&&& CPU affinity:中文唤作“CPU亲和性”,是指在CMP架构下,能够将一个或多个进程绑定到一个或多个处理器上运行。[]
请先确定你的cpu核心及命名(例如四个核心:0,1,2,3):cat /proc/cpuinfo
taskset -cp 1 5200
#把PID为5200的进程运行到CPU#1上
#也可以在启动进程时指定:
taskset -c 1 /etc/init.d/mysql start
linux taskset命令详解
SYNOPSIS
&&&&&& taskset [options] [mask | list ] [pid | command [arg]...]
OPTIONS
&&&&&& -p, --pid
&&&&&&&&&&&&& operate on an existing PID and not launch a new task
&&&&&& -c, --cpu-list
&&&&&&&&&&&&& specifiy& a& numerical& list of processors instead of a bitmask.
&&&&&&&&&&&&& The list may contain multiple items,& separated& by& comma,& and
&&&&&&&&&&&&& ranges.& For example, 0,5,7,9-11.
&&&&&& -h, --help
&&&&&&&&&&&&& display usage information and exit
&&&&&& -V, --version
&&&&&&&&&&&&& output version information and exit
四、其他
参考文献:张磊blog
浏览 12741
论坛回复 /
(0 / 4509)
浏览: 330511 次
来自: 福建
forcer521 写道这个是什么问题呢,启动了访问报错:引用 ...
这个是什么问题呢,启动了访问报错:引用An Exception ...
很强大的一篇文章,看了大有帮助
文章不错,如果加上适当的说明,就更好了。学习中...
目前正在用vi/vim 觉得够用了。。看到这张图才知道 原来
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'c#教程之在桌面应用程序中实现多任务处理
来源:未知
在桌面应用程序中实现多任务处理
多任务处理(Multitasking)是指同时做多件事情的能力。就在不久之前,它还是一种很容 易解释,但很难实现的一个概念。 在最理想的情况中,在多核处理器上运行的应用程序应该执行跟处理器的内核数一样多的 并发任务,让每个内核都&忙&起来。62然而,在实现并发性时,有许多问题需要考虑, 包括:
l&& 如果将一个应用程序分解成一组并发操作?
l&& 如何安排一组操作在多个处理器上并发执行?
l&& 如何保证只执行处理器数量那么多的并发操作?
l&& 如果一个操作被阻塞(比如在等待 I/O 操作完成的时候),如何检测到这个情况,并安 排处理器运行另一个操作,而不是在那里傻傻地等待?
l&& 如何知道一个或者多个并发操作已经完成?
l&& 如何同步对共享数据的访问,防止两个或多个并发操作不慎破坏对方的数据? 对于应用程序的开发人员,第一个问题是应用程序设计的问题。其余问题都可以依赖一个 编程基础结构来解决。Microsoft 提供了&任务并行库&(Task Parallel Library,TPL)来帮助 解决这些问题。
在第 28 章,会讲到一些面向查询的问题天生就应该用并行方案来解决,以及如何使用 PLINQ 的 ParallelEnumerable 类型对查询操作进行并行处理。然而,有的时候,你需要为一些较常 规的情况采用一种命令式63的解决方案。TPL 包含一组类型和操作,允许更开发人员明确指
62 &或者说一直让 CPU 处于&饱和&状态。如果同时运行的线程数超过核数,称为&过饱和&;如果少于核数,称为&欠饱和&。
63 &Imperative Programming(命令式编程)的意思是,程序员需要一步步写清楚程序需要如何做什么(How to do What)。Declarative Programming(声明式编程)的意思是,程序员不需要一步步告诉程序如何做,只需要告诉程序在哪些地方做什么(Where to do What)。比起 Imperative Programming(命令式编程)来,Declarative Programming(声明式编程)是在一个更高的层次 上编程。Declarative Programming 编程语言是更高级的语言。Declarative Programming 通常处理一些总结性、总览性的工作,
定如何将一个应用程序分解成一组并行任务。
27.2.1 任务、线程和线程池
TPL 中最重要的类型就是 Task 类。Task 类是对一个并发操作的抽象。要创建一个 Task 对象 来运行一个代码块。可实例化多个 Task 对象。然后,如果有足够数量的处理器(或内核), 就可以让它们并发运行。
注意:从现在起,不再区分处理器和内核,一概称为&处理器&。
在内部,TPL 使用 Thread 对象和 ThreadPool 类实现任务,并调度它们的执行。多线程处理 和线程池自.NET Framework 1.0 起便可以使用,完全可以直接在代码中使用 System.Threading 命名空间中的 Thread 类。但是自.NET &Framework 4.0 引入的 TPL 提供了一个额外的抽象等 级,使你能够简单地区分应用程序的并行度(任务)和并行单位(线程)。在单处理器计算 机上,这两者通常是相同的。然而,在多处理器计算机上,两者却是不同的。如果直接依 赖线程设计一个程序,会发现应用程序的伸缩性不是特别好;程序会使用你显式创建的那 些数量的线程,操作系统只会调度那些数量的线程。如果线程数显著超过可用的处理器数 量,可能造成 CPU&过饱和&(过载)以及较差的响应能力。如果线程数少于可用的处理器 数量,则可能造成 CPU&欠饱和&(欠载),大量处理能力被白白浪费了。
TPL 对实现一组并发任务所需的线程数量进行了优化,并根据可用的处理器数量调度它们。 TPL 使用的是.NET Framework 线程池提供的一组线程,并实现了一个查询机制,可有效地在 这些线程之间分布工作负荷。程序创建一个 Task 对象时,任务会添加到一个全局队列。等 一个线程可用时,任务就从全局队列移除,并由那个线程执行。ThreadPool &实现了大量优 化措施,使用一个所谓的&工作窃取&(work-stealing)算法64确保线程得到高效调度。
注意:ThreadPool 在.NET Framework 以前的版本中便已存在。在.NET Framework 4.0 中,它
进行了显著增强以支持&任务&。
要注意的是,.NET Framework 创建的用来处理任务的线程数量并不一定就是处理器的数量。 取决于当前工作负荷的本质,一个或多个处理器可能要忙于为其他应用程序和服务执行高 优先级的工作。其结果就是,你的应用程序的最优线程数可能少于机器中的处理器数量。 另外,应用程序的一个或多个线程可能要等待一个耗时的内存访问、I/O 操作或网络操作完 成,使对应的处理器变得空闲。在这种情况下,最优的线程数可能多于可用的处理器数 量。.NET Framework 采用一个所谓的&爬山&算法来动态判断当前工作负荷下的理想线程
不适合做顺序相关的细节相关的底层工作。&&译者注
64 &简单地说,就是一个池程池线程空闲时,根据一定的算法知道自己在可以预见的将来不是特别忙,所以从另一个线程池线程 的工作项队列&窃取&一个工作项来进行处理。&&译者注
这里的重点在于,你在代码中唯一要做的就是将应用程序分解成可并行运行的任务。.NET Framework 负责根据处理器和计算机的工作负荷创建适当数量的线程,将你的任务和这些 线程关联,并安排它们高效运行。将工作分解成太多的任务是没有关系的,因为.NET Framework 会运行符合实际情况的那么多的并发线程;事实上,鼓励你对自己的工作进行 细致分解,这有助于确保应用程序的伸缩性(拿到处理器数量更多的计算机上运行时,运 行时间会缩短)。
27.2.2& &创建、运行和控制任务
Task 对象和 TPL 中的其他类型位于 System.Threading.Tasks 命名空间。可使用 Task 构造器创 建 Task 对象。Task 构造器有多个重载版本,但在所有版本的参数中,都要求提供一个 Action 委托。第 23 章讲过,Action 委托引用一个不返回值的方法(一个&行动&)。一个任务对象 在被调度时,就会运行委托指定的方法。下例创建一个 Task 对象,它通过一个委托运行一 个名为 doWork 的方法(还可使用匿名方法或 lambda 表达式来指定,如注释中的代码所示):
Task task = new Task(new Action(doWork));
// Task task = new Task(delegate { this.doWork(); });
// Task task = new Task(() =& { this.doWork(); });
private void doWork()
// 任务启动时会运行这里的代码
许多情况下,都可以让编译器推断 Action 委托类型本身,所以可以直接指定要运行的 方法。例如,上面的例子可以简化成:
Task task = new Task(doWork);
编译器实现的委托推断规则不仅应用于 Action 类型,而且在能使用委托的任何地方都 能使用。在本章剩余部分,你会看到许多这样的例子。
默认的 Action 类型引用一个不获取任何参数的方法。Task 构造器的其他重载版本可以获取 一个 Action&object&参数,它代表的委托引用了一个获取单个 object 参数的方法。这些重载 允许向任务运行的方法传入数据。以下代码展示了一个例子:
Action&object& action = doWorkWithO object parameterData = ...;
Task task = new Task(action, parameterData);
private void doWorkWithObject(object o)
创建好一个 Task 对象后,可以使用 Start 方法启动它,如下所示:
Task task = new Task(...);
task.Start();
Start 方法也进行了重载,可选择指定一个 TaskScheduler 对象来控制并发度和其他调度选项。 建议使用.NET Framework 内建的默认 TaskScheduler 对象。但是,如果想对任务的请求和调 度方式进行更大的控制,也可以定义自定义的 TaskScheduler 类。这方面的细节超出了本书 的范围。如果你想更多地了解 TaskScheduler &抽象类,请参见 V isual &Studio &提供的.NET Framework 类库文档。65
可以使用 TaskScheduler 类的静态 Default 属性,获得对默认的 TaskScheduler 对象的一个引 用。TaskScheduler 类还提供了静态 Current 属性,它返回对目前使用的 TaskScheduler 对象 的引用。(如果不显式指定一个调度器,就会使用这个 TaskScheduler 对象。)一个特定的任 务可以向默认 TaskScheduler 提出一些&提议&,提议它们应该如何调度和运行任务。这是 通过在 Task& &构 造 器 中 指 定 一 个 TaskCreationOptions& &枚 举 值 来 实 现 的 。 欲 知 TaskCreationOptions 枚举的详情,请参见 V isual Studio 提供的.NET Framework 类库文档。 任务运行的方法结束后,任务会结束,用于运行任务的线程会返回线程池,以便执行另一
个任务。 通常,调度器会安排任务尽可能地并行执行(异步)。但是,也可通过创建一个&延续&
(continuation),从而安排几个任务顺序执行(同步)。我们是调用 Task 对象的 ContinueWith 方法来创建一个延续任务。一个 Task 对象的操作完成后,调度器自动创建一个新的 Task 对 象,它将运行由 ContinueWith 方法指定的操作。一个&延续&所指定的方法要求获取一个 Task 参数,调度器会向方法传递对已完成的任务的引用。ContinueWith 的返回值是对一个 新 Task 对象的引用。下例创建一个 Task 对象,它运行 doWork 方法,并通过一个&延续& 指定在第一个任务完成后,在一个新任务中运行 doMoreWork 方法。
Task task = new Task(doWork);
task.Start();
Task newTask = task.ContinueWith(doMoreWork);
private void doWork()
// 任务开始时运行这里的代码
private void doMoreWork(Task task)
// doWork 结束后运行这里的代码
65 &也可参考 Jeffery Richter 的《CLR via C#第三版》,本人翻译,清华大学出版社 2010 年出版。&&译者注
ContinueWith 方法有大量重载版本,可以提供大量参数来指定额外的项,比如指定一个要
使用的 TaskScheduler 和一个 TaskContinuationOptions 值。TaskContinuationOptions 是一个枚 举类型,它包含了 TaskCreationOptions 枚举值的一个超集。额外的、跟任务延续有关的值 包括
l&& NotOnCanceled 和 OnlyOnCanceled&&&& &NotOnCanceled 选项指定只有当上一个行动顺利 完成,没有被中途取消,延续任务才应该运行。而 OnlyOnCanceled 选项指定只有在上 一个行动被取消的前提下,才应该运行这个延续任务。本章后面的 27.4 节&取消任务 和处理异常&会讲述如何取消一个任务。
l&& NotOnFaulted 和 OnlyOnFaulted&& &NotOnFaulted 选项指定只有当上一个行动顺利完成, 没有抛出一个未处理的异常,才应该运行延续任务。OnlyOnFaulted 选项则指定只有在 上一个行动抛出未处理异常的前提下,才运行延续任务。27.4 节&取消任务和处理异 常&会更详细地讨论如何管理任务中发生的异常。
l&& NotOnRanToCompletion 和 OnlyOnRanToCompletion&&&&& NotOnRanToCompletion 选项指定 只有在上一个操作没有成功完成的情况下才运行延续任务。没成功完成要么是被取消, 要么是指抛出一个异常。OnlyOnRanToCompletion 造成延续任务只有在上一个操作成功 完成的情况下才运行。
以下代码展示了如何为一个任务添加一个延续任务,只有在初始操作没有抛出未处理异常
的情况下才运行延续任务。
Task task = new Task(doWork);
task.ContinueWith(doMoreWork, TaskContinuationOptions.NotOnFaulted);
task.Start();
如果通常使用同一组 TaskCreationOptions 值和相同的 TaskScheduler 对象,可以用 TaskFactory 对象在一个步骤中完成任务的创建和运行。TaskFactory 类的构造器允许你指定任务调度器、 任务创建选项以及任务延续选项,这个工厂构造的任务将使用这些选项。TaskFactory 类提 供了 StartNew 方法来创建并运行一个 Task 对象。和 Task 类的 Start 方法相似,StartNew 方 法有多个重载版本,但所有版本都要获取对任务应该运行的一个方法的引用。 以下代码展示了如何使用同一个任务工厂创建并运行两个任务:
TaskScheduler scheduler = TaskScheduler.C
TaskFactory taskFactory = new TaskFactory(scheduler, TaskCreationOptions.None, TaskContinuationOptions.NotOnFaulted);
Task task = taskFactory.StartNew(doWork);
Task task2 = taskFactory.StartNew(doMoreWork);
即使当前没有指定任何特定的任务创建选项,使用的也是默认任务调度器,仍然应该考虑 使用一个 TaskFactory 对象。它能保证一致性。另外,如果以后需要对这个过程进行修改, 那么只需修改少量代码,即可确保所有任务仍然以相同的方式运行。Task 类通过静态 Factory 属性公开了 TPL 使用的默认 TaskFactory。可以像下面这样使用它:
Task task = Task.Factory.StartNew(doWork);
执行并行操作的应用程序经常都需要对任务进行同步66。Task 类提供了 Wait 方法,它实现 了一个简单的任务协作方法。它允许挂起(暂停)当前线程的执行,直到指定的任务完成,
如下所示:
task2.Wait(); // 等待,直到task2 完成
可以使用 Task 类的静态 WaitAll 和 WaitAny 方法等待一组任务。两个方法都获取包含一组 Task 对象的参数数组。WaitAll 方法一直等到指定的所有任务都完成,而 WaitAny 等待指定 的至少一个任务完成。可以像下面这样使用它们:
Task.WaitAll(task, task2); // 等待task 和task2 都完成
Task.WaitAny(task, task2); // 等待task 或task2 完成
27.2.3& &使用 Task 类实现并行处理
在下一个练习中,将使用 Task 类对应用程序中的处理器密集型代码进行并行处理。由于计 算由多个处理器分担,所以能缩短应用程序的运行时间。这个应用程序称为 GraphDemo, 它包含一个 WPF 窗体,窗体上用一个 Image 控件显示一个图表。应用程序执行一个复杂的 计算在图表上画点。
注意:本章的练习设计在安装了多个处理器(或者安装了一个多核处理器)的计算机上运
行。如果使用的单处理器或者单核 CPU,就体验不到相同的效果。另外,在练习之间,不 应启动任务额外的线程,因为这可能影响到结果。
&O& &检查并运行 GraphDemo 单线程应用程序
1.&& 如果 Microsoft V isual Studio 2010 还没有启动,就启动它。
2.&& 打 开 & 文 档 & 文 件 夹 下 的 \Microsoft& &Press\V isual& &CSharp& &Step& &By& &Step\Chapter
27\GraphDemo 子文件夹中的 GraphDemo 项目。
3.&& 在解决方案资源管理器中,双击 GraphDemo 项目中的 GraphWindow.xaml 文件,显示 窗体的设计视图。
窗体中包含以下控件:
l&& 一个名为 graphImage 的 Image 控件,它显示由应用程序渲染的图表。
l&& 一个名为 plotButton &的 Button &控件,用户单击这个按钮为图表生成数据,并在
graphImage 控件中显示它。
l&& 一个名为 duration 的 Label 控件,应用程序在这个标签中显示生成并渲染数据所花的时 间。
66 同步意味着不能同时访问一个资源,只有在你用完了之后,我才能接着用。在多线程编程中,&同步&(Synchronizing)的 定义是:当两个或更多的线程需要存取共同的资源时,必须确定在同一时间点只有一个线程能存取共同的资源,而实现这 个目标的过程就称为&同步&。&&译者注
4.&&& &在解决方案资源管理器中,展开 GraphWindow.xaml,双击 GraphWindow.xaml.cs,在
&代码和文本编辑器&窗口中显示它的代码。
窗体用一个名为 graphBitmap 的 System.Windows.Media.Imaging.WriteableBitmap 对象渲染图 表。pixelWidth 和 pixelHeight 这两个变量分别指定了 WriteableBitmap 对象的水平和垂直分 辨率。dpiX 和 dpiY 这两个变量分别指定了图像中的水平和垂直密度,单位是 DPI(每英寸 点数)。
public partial class GraphWindow : Window
private static long availableMemorySize = 0;
private int pixelWidth = 0; private int pixelHeight = 0; private double dpiX = 96.0; private double dpiY = 96.0;
private WriteableBitmap graphBitmap =
5.&& 检查 GraphWindow 构造器,如下所示:
public GraphWindow()
InitializeComponent();
PerformanceCounter memCounter = new PerformanceCounter(&Memory&, &Available Bytes&);
availableMemorySize = Convert.ToUInt64(memCounter.NextValue());
this.pixelWidth = (int)availablePhysicalMemory / 20000;
if (this.pixelWidth & 0 || this.pixelWidth & 15000)
this.pixelWidth = 15000;
this.pixelHeight = (int)availablePhysicalMemory / 40000;
if (this.pixelHeight & 0 || this.pixelHeight & 7500)
this.pixelHeight = 7500;
为了避免耗尽计算机的所有可用内存,并生成 OutOfMemory 异常,这个应用程序创建了一 个 PerformanceCounter 对象来查询计算机的可用物理内存容量。然后,它根据这个信息为 pixelWidth &和 pixelHeight &变量确定恰当的值。计算机上的内存越多,为 pixelWidth &和 pixelHeight 变量生成的值越大(但不会超过这两个值的上限,分别是 15000 和 7500),而且 使用 TPL 来进行并行处理的优势越大。然而,如果这个应用程序在你的计算机上仍然生成 了 OutOfMemory 异常,请自行增大为 pixelWidth 和 pixelHeight 生成值时所用的除数(20000 和 40000)。
如果你的计算机有大量内存,为 pixelWidth 和 pixelHeight 计算出来的值可能会溢出。在这
种情况下,它们将包含负值,应用程序会抛出异常。构造器中的代码会检测这种情况,并 将 pixelWidth 和 pixelHeight 字段设为一对有用的值,使应用程序在这种情况下能正确运行。
6.&&& &检查 plotButton_Click 方法的代码:
private void plotButton_Click(object sender, RoutedEventArgs e)
if (graphBitmap == null)
graphBitmap = new WriteableBitmap(pixelWidth, pixelHeight, dpiX, dpiY, PixelFormats.Gray8, null);
int bytesPerPixel = (graphBitmap.Format.BitsPerPixel + 7) / 8;
int stride = bytesPerPixel * graphBitmap.PixelW
int dataSize = stride * graphBitmap.PixelH
byte [] data = new byte[dataSize];
Stopwatch watch = Stopwatch.StartNew();
generateGraphData(data);
duration.Content = string.Format(&Duration (ms): {0}&, watch.ElapsedMilliseconds);
graphBitmap.WritePixels(
new Int32Rect(0, 0, graphBitmap.PixelWidth, graphBitmap.PixelHeight), data, stride, 0);
graphImage.Source = graphB
单击 plotButton 按钮就会运行这个方法。代码实例化 graphBitmap 对象(如果还没有被创建 的话),并指定每个像素都代表一个灰阶,每像素 8 位。这个方法了使用以下变量和方法:
l&& bytesPerPixel 变量计算容纳每个像素所需的字节数。(WriteableBitmap 类型支持一系列 像素格式,每像素最多 128 位以显示全彩图像。)
l&& stride 变量以字节为单位指定了在 WriteableBitmap 对象中,相邻的像素之间的垂直距
l&& dataSize 变量计算容纳 WriteableBitmap 对象的数据所需的字节数。这个变量用于将 data
数组初始化为恰当的大小。
l&& data 字节数组容纳了图表的数据。
l&& watch 变量是一个 System.Diagnostics.Stopwatch 对象。StopWatch 类型用于精确计时。 该类型的静态 StartNew 方法创建 StopWatch 对象的一个新实例,并启动它。可以查询 ElapsedMilliseconds 属性来查询 StopWatch 对象的运行时间。
l&& generateGraphData 方法在 data 数组中填充要由 WriteableBitmap 对象显示的那个图表 的数据。将在下一步检查这个方法。
l&& WriteableBitmap 类的 WritePixels 方法将数据从一个字节数组复制到一个位图以进行渲 染。该方法获取一个 Int32Rect 参数(指定 WriteableBitmap 对象中要填充的矩形区域)、 要复制到 WriteableBitmap 对象的数据(像素数组)、WriteableBitmap 对象中的相邻像 素的垂直距离(步幅)以及 WriteableBitmap 对象中的一个偏移量(将从这个偏移位置 处开始写入数据)。
注意:可以使用 WritePixels 方法选择性地覆盖 WriteableBitmap 对象中的信息。在这个例子
中,是覆盖全部内容。欲知 WriteableBitmap 类的详情,请参见随同 V isual Studio 2010 安装 的.NET Framework 类库文档。
67 &也称为扫描宽度,或者步幅。&&译者注
l&& Image 控件的 Source 属性指定了 Image 控件应该渲染的数据。本例将 Source 属性设为
WriteableBitmap 对象。
7.&& 检查 generateGraphData 方法的代码:
private void generateGraphData(byte[] data)
int a = pixelWidth / 2;
int b = a *
int c = pixelHeight / 2;
for (int x = 0; x & x ++)
int s = x *
double p = Math.Sqrt(b - s);
for (double i = -p; i & i += 3)
double r = Math.Sqrt(s + i * i) / double q = (r - 1) * Math.Sin(24 * r); double y = i / 3 + (q * c);
plotXY(data, (int)(-x + (pixelWidth / 2)), (int)(y + (pixelHeight / 2)));
plotXY(data, (int)(x + (pixelWidth / 2)), (int)(y + (pixelHeight / 2)));
这个方法执行一系列计算为一幅相当复杂的图表画点。(实际的计算方式并不重要&&它只 是生成一幅看起来相当复杂的图表而已!)计算每个点时,都调用 plotXY 方法,在与这个点 对应的 data 数组中设置恰当的字节。图表的点围绕 X 轴反射,所以每一个计算都要调用两 次 plotXY 方法:一次针对 X 轴的正值,另一次针对负值。
8.&&& &检查 plotXY 方法:
private void plotXY(byte[] data, int x, int y)
data[x + y * pixelWidth] = 0xFF;
这是一个很简单的方法,它在 data 数组中设置与作为参数传递的 X 和 Y 坐标对应的字节。 值 0xFF 指出在渲染图表时,对应的像素应设为白色。任何没有设置的像素都显示成黑色。
9.&& 在&调试&菜单中选择&开始执行(不调试)&来生成并运行应用程序。
10.& &出现 Graph Demo 窗口后,单击 Plot Graph,然后耐心等待。 应用程序要花几秒钟的时间生成并显示图表。下图是一个例子。请注意 Duration &(ms)标签 中的值。在本例中,应用程序花了 3440 毫秒来完成图表的渲染。
注意:这个应用程序是在一台安装了 Intel& Core 2 Duo E GHz)处理器,内存为 4 GB
的机器上运行的。如果你的计算机使用了不同的处理器或者不同的内存容量,结果可能有 所不同。另外,你会注意到它最开始显示图表所花的时间长于报告的时间。这是因为初始 化数据结构来实际显示图表所需的时间没有被计算在内(这是 graphBitmap &控件的 WritePixels 方法的一部分)。以后再运行时,便不会产生这个开销了。
11.& &再次单击 Plot Graph 按钮,注意所花的时间。多次重复这个操作,获得一个平均值。
12.& &按 Ctrl+Shift+Esc 打开&任务管理器&。
13.& &在 Windows 任务管理器中,单击&性能&标签。
14.& &返回 Graph Demo 窗口,单击 Plot Graph。
15.& &在 Windows 任务管理器中,注意在生成图表期间 CPU 使用率的最大值。具体结果在不 同机器上是不同的。但在双核机器上,CPU 使用率可能在 50%到 60%之间,如下图所示。 在四核机器上,CPU 使用率可能在 30%以下。
16.& &返回 Graph Demo 窗口,再次单击 Plot Graph。注意 Windows 任务管理器中显示的 CPU
使用率。重复几次这个操作,得到一个平均值。
17.& &关闭 Graph Demo 窗口,最小化 Windows 任务管理器。 现在,你对这个应用程序执行计算所花的时间已经有了一个基本的把握。然而,根据 Windows 任务管理器显示的 CPU 使用率,可以清楚地看出应用程序并没有最充分地利用处 理资源。在双核机器上,它只利用了 CPU 计算能力的一半;在四核机器上,只利用了 1/4。 之所以会有这个现象,是因为应用程序是单线程的。而在一个 Windows 应用程序中,单线 程只能占用多核处理器中的一个内核。要将负荷分散到所有可用的内核上,必须将应用程 序分解成任务,并安排每个任务由不同内核上运行的一个单独的线程来执行。
&O& &修改 GraphDemo 应用程序来使用并行线程
1.&& 返回 V isual Studio 2010,在&代码和文本编辑器&窗口中显示 GraphWindow.xaml.cs 文
2.&& 检查 generateGraphData 方法。
仔细思考一下,就知道这个方法的目的是在 data 数组中填充项。外层 for 循环基于 x
循环控制变量来遍历数组,如以下加粗的代码所示:
private void generateGraphData(byte[] data)
int a = pixelWidth / 2;
int b = a *
int c = pixelHeight / 2;
for (int x = 0; x & x ++)
int s = x *
double p = Math.Sqrt(b - s);
for (double i = -p; i & i += 3)
double r = Math.Sqrt(s + i * i) / double q = (r - 1) * Math.Sin(24 * r); double y = i / 3 + (q * c);
plotXY(data, (int)(-x + (pixelWidth / 2)), (int)(y + (pixelHeight / 2)));
plotXY(data, (int)(x + (pixelWidth / 2)), (int)(y + (pixelHeight / 2)));
在这个循环中,每一次迭代所执行的计算独立于其他迭代执行的计算。因此,完全可以将 对这个循环执行的工作进行分解,在单独的处理器上运行每一次迭代。
3.&& 修改 generateGraphData 方法的定义,让它获取两个额外的 int 参数,名为 partitionStart
和 partitionEnd,如以下加粗的代码所示:
private void generateGraphData(byte[] data, int partitionStart, int partitionEnd)
4.&& 在 generateGraphData 方法中,更改外层 for 循环,在 partitionStart 和 partitionEnd 之 间遍历,如加粗的代码所示:
private void generateGraphData(byte[] data, int partitionStart, int partitionEnd)
for (int x = partitionS x & partitionE x ++)
5.&& 在&代码和文本编辑器&窗口中,在 GraphWindow.xaml.cs 文件顶部添加以下 using 语 句:
using System.Threading.T
6.&& 在 plotButton_Click 方法中,将调用 generateGraphData 方法的语句注释掉,添加以下 加粗的语句来创建一个 Task 对象(使用默认 TaskFactory 对象),并启动它
Stopwatch watch = Stopwatch.StartNew();
// generateGraphData(data);
Task first = Task.Factory.StartNew(() =& generateGraphData(data, 0, pixelWidth / 4));
任务将运行由 lambda 表达式指定的代码。partitionStart 和 partitionEnd 参数值指出 Task 对
象将计算图表前半部分的数据。(完整图表数据是为 0 到 pixelWidth / 2 之间的值描绘的点。)
7.&& 添加另一个语句,在另一个线程上创建并运行另一个 Task 对象,如以下加粗的代码所 示:
Task first = Task.Factory.StartNew(() =& generateGraphData(data, 0, pixelWidth / 4));
Task second = Task.Factory.StartNew(() =& generateGraphData(data, pixelWidth / 4,
pixelWidth / 2));
这个 Task 对象调用 generateGraph 方法,为 pixelWidth / 4 &到 pixelWidth / 2 的值计算数据。
8.&&& &添加以下语句,等待两个 Task 对象都结束再继续:
Task.WaitAll(first, second);
9.&&& &在&调试&菜单中选择&开始执行(不调试)&来生成并运行应用程序。
10.& &显示 Windows 任务管理器,单击&性能&标签
11.& &返回 Graph Demo 窗口,单击 Plot Graph。在 Windows 任务管理器,注意生成图表期间 的 CPU 使用率的最大值。这个图表在 Graph &Demo 窗口中显示后,记下生成图表所花的时 间。重复几次,获得一个平均值。
12.& &关闭 Graph Demo 窗口,最小化 Windows 任务管理器。
这一次,应用程序的运行速度比以前快得多。
在我的计算机上,时间缩短至 2451 毫秒&&比以前减少了约 30%的时间。除此之外,还会 注意到应用程序使用了 CPU 的多个内核。在双核机器上,CPU 峰值使用率能达到 100%。如 果使用的是四核机器,CPU 峰值使用率不会这么高,但也能达到 50%以上。这是由于仍然 有两个内核没有用到。要在四核机器上体会这一点,并进一步缩短时间,请添加另外两个 Task 对象,在 plotButton_Click 方法中将工作分解成四个部分。如以下加粗的代码所示:
Task first = Task.Factory.StartNew(() =& generateGraphData(data, 0, pixelWidth / 8)); Task second = Task.Factory.StartNew(() =& generateGraphData(data, pixelWidth / 8,
pixelWidth / 4));
Task third = Task.Factory.StartNew(() =& generateGraphData(data, pixelWidth / 4, pixelWidth * 3 / 8));
Task fourth = Task.Factory.StartNew(() =& generateGraphData(data, pixelWidth * 3 / 8, pixelWidth / 2));
Task.WaitAll(first, second, third, fourth);
即使只有双核系统,也可尝试这个修改,执行时间仍可从中受益。这主要是由于 TPL 设计 得非常高效,而且.NET Framework 能对代码进行优化,为每个任务都高效地调度线程。
27.2.4& &使用 Parallel 类对任务进行抽象
使用 Task 类,可以对应用程序创建的任务数量进行完全的控制。然而,必须修改应用程序 的设计来适应 Task 对象的引入。还必须添加代码对操作进行同步;应用程序只有在所有任
务都完成之后,才能对图表进行渲染。在复杂的应用程序中,任务的同步会变成一个重要
的过程,而且很容易犯错误。
TPL 的 Parallel 类允许对一些常见的编程构造进行&并行化&,同时不要求你重新设计应用程 序。在内部,Parallel 类会创建它自己的一组 Task 对象,并在这些任务完成时自动同步。Parallel 类在 System.Threading.Tasks 命名空间中,它提供了少量静态方法,可用这些方法指定应尽 量并行运行的代码。这些方法包括:
l&& Parallel.For&& &用这个方法代替 C# for 语句。它定义了一个循环,该循环的每一次迭代都 使用任务来并行运行。这个方法有大量重载版本(9 个),但每个版本的基本原理是相 同的。都要指定一个起始值,和一个结束值,并指定一个方法引用,该方法要求获取 一个整数参数。针对从起始值开始,一直到结束值减 1 的每一个值,方法都会执行一 次,参数将用代表当前值的一个整数来填充。例如,在单线程的情况下,以下简单的 for 循环将顺序执行每一次迭代:
for (int x = 0; x & 100; x++)
// 进行处理
取决于循环主体执行的是什么处理,也许能将这个循环替换成一个 Parallel.For 构造,它以 并行方式执行迭代,如下所示:
Parallel.For(0, 100, performLoopProcessing);
private void performLoopProcessing(int x)
// 执行处理
利用 Parallel.For 方法的重载版本,可以提供对于每个线程来说都是私有的局部数据,可以 指定 For 方法运行的任务的创建选项,并可创建一个 ParallelLoopState 对象,以便将状态信 息传给循环的其他并发迭代。(ParallelLoopState 对象的用法将在本章以后介绍。)
l&& Parallel.ForEach&T&&& 用这个方法代替 C# foreach 语句。和 For 方法相似,ForEach 定义 了一个循环,其中每一次迭代都并行运行。要指定一个实现了 IEnumerable&T&泛型接 口的集合对象,还要指定一个方法引用,该方法要获取一个 T 类型的参数。针对集合 中的每一项,都会执行该方法,当前项作为参数传给方法。利用这个方法的重载版本, 可以提供私有的、局部于线程的数据,并可指定 ForEach &方法所运行的任务的创建选 项。
l&& Parallel.Invoke&& &以并行任务的形式执行一组无参方法。要指定无参、而且无返回值的 一组委托方法调用(或 lambda 表达式)。每个方法调用都可以在一个单独的线程上运 行(以任何顺序)。例如,以下代码发出了一系列方法调用:
doWork(); doMoreWork(); doYetMoreWork();
可将上述语句替换成以下代码,它们用一系列任务调用这些方法:
Parallel.Invoke( doWork, doMoreWork,
doYetMoreWork
要注意的是,最终是由.NET Framework 根据环境和当前的工作负荷决定实际的并行度。例 如,如果用 Parallel.For 实现一个迭代 1000 次的循环,.NET &Framewor 并非一定创建 1000 个并发的任务(除非你的处理器有 1000 个内核)。相反,.NET Framework 会创建它认为的 最佳数量的任务,在可用资源和保持处理器&饱和&之间取得一个平衡。一个简单的任务 可能执行多次迭代,任务和任务相互协作,以确定每个任务要执行哪些迭代。因此,作为 开发人员,我们不能对迭代的执行顺序做出任何假设。因此,必须保证迭代和迭代之间没 有依赖性;否则就可能得到出乎预料的结果,本章稍后会对此进行演示。 在下一个练习中,将返回 GraphData 应用程序的原始版本,并用 Parallel 类并行地执行操作。
&O& &在 GraphData 应用程序中使用 Parallel 并发地执行操作
1.&& 在 V isual Studio 2010 中,打开&文档&文件夹下的\Microsoft Press\V isual CSharp Step By
Step\Chapter 27\GraphDemo Using the Parallel Class 子文件夹中的 GraphDemo &解决方案。 这是原始 GraphDemo 应用程序的一个副本。它目前还没有任何任务。
2.&& 在解决方案资源管理器中,展开 GraphDemo 项目中的 GraphWindow.xaml 节点。双击
GraphWindow.xaml.cs,在&代码和文本编辑器&窗口中显示窗体的代码。
3.&& 在文件顶部添加以下 using 语句:
using System.Threading.T
4.&& 找到 generateGraphData 方法,如下所示:
private void generateGraphData(byte[] data)
int a = pixelWidth / 2;
int b = a *
int c = pixelHeight / 2;
for (int x = 0; x & x++)
int s = x *
double p = Math.Sqrt(b - s);
for (double i = -p; i & i += 3)
double r = Math.Sqrt(s + i * i) / double q = (r - 1) * Math.Sin(24 * r); double y = i / 3 + (q * c);
plotXY(data, (int)(-x + (pixelWidth / 2)), (int)(y + (pixelHeight / 2)));
plotXY(data, (int)(x + (pixelWidth / 2)), (int)(y + (pixelHeight / 2)));
对整数变量 x 的值进行遍历的外层 for 循环最适合&并行化&。你可能还想对基于变量 i 的
内层循环进行&并行化&。但是,这个循环要花费额外的精力才能实现并行化,这都是由于
i 的类型造成的。(Parallel 类的方法要求控制变量是一个整数。)除此之外,如果有像这样 的嵌套循环,一个良好的编程实践是先对外层循环进行并行化,再测试应用程序的性能是 否得到了足够的优化。如果还不理想,再对嵌套循环进行处理,由外向内进行并行化。每 一级循环在并行化之后,都要测试一下性能。许多情况下,外层循环的并行化对性能的影 响最大,修改内层循环所产生的收益会越来越小。
5.&&& &移走 for &循环主体的代码,用这些代码创建一个新的 private &void &方法,名为 calculateData。calculateData 方法获取一个名为 x 的整数参数和一个名为 data 的字节数组。 另外,将声明局部变量 a,b 和 c 的语句从 generateGraphData 方法移到 calculateData 方法 的起始处。以下代码展示了 generateGraphData 方法和 calculateData 方法(暂时不要编译):
private void generateGraphData(byte[] data)
for (int x = 0; x & x++)
private void calculateData(int x, byte[] data)
int a = pixelWidth / 2;
int b = a *
int c = pixelHeight / 2;
int s = x *
double p = Math.Sqrt(b - s);
for (double i = -p; i & i += 3)
double r = Math.Sqrt(s + i * i) / double q = (r - 1) * Math.Sin(24 * r); double y = i / 3 + (q * c);
plotXY(data, (int)(-x + (pixelWidth / 2)), (int)(y + (pixelHeight / 2)));
plotXY(data, (int)(x + (pixelWidth / 2)), (int)(y + (pixelHeight / 2)));
6.&& 在 generateGraphData 方法中,将 for 循环更改为调用静态 Paralle.For 方法的一个语句, 如以下加粗的部分所示:
private void generateGraphData(byte[] data)
Parallel.For (0, pixelWidth / 2, (int x) =& { calculateData(x, data); });
上述代码是原始 for 循环的并行版本。它遍历从 0 到 pixelWidth / 2 && 1 的值。每一次调用 都用一个任务来运行。(每个任务都可能运行多次迭代。)Parallel.For 方法只有在所有任务 都完成它们的工作之后才会结束。记住,Parallel.For 方法要求最后一个参数是一个获取单 个整数参数的方法。它调用这个方法,并传递当前循环索引作为参数。在本例中, calculateData &方法和要求的签名不匹配,因为它要获取两个参数:一个整数和一个字节数
组。因此,代码用一个 lambda 表达式定义一个具有正确签名的匿名方法,再把它作为一个
适配器来调用 calculateData 方法,并传递正确的参数。
7.&&& &在&调试&菜单中选择&开始执行(不调试)&来生成并运行应用程序。
8.&&& &显示 Windows 任务管理器,单击&性能&标签。
9.&&& &返回 Graph Demo 窗口,单击 Plot Graph 按钮。在 Windows 任务管理器中,注意在生 成图表期间的峰值 CPU 使用率。等图表在 Graph &Demo 窗口中显示之后,记录生成图表所 花的时间。重复几次这个操作,得到一个平均值。
10.& &关闭 Graph Demo 窗口,最小化 Windows Task Manager。 你会注意到应用程序的速度和上一个使用 Task 对象的版本差不多(而且可能要稍快一些, 具体取决于 CPU 的核数),CPU 峰值使用率应该达到 100%。68
27.2.5& &什么时候不使用 Parallel 类
应该注意的是,虽然 V isual Studio 开发团队尽了最大努力,Parallel 类仍然不是万能的;不 能不假思索地使用它,然后就指望自己的应用程序突然变快了,而且能获得和原来一样的 计算结果。Parallel 类的作用是对代码中的计算限制的独立区域进行并行化。请注意两个关 键词,计算限制和独立。如果代码不是计算限制的69,并行化就不一定能提升性能。下一 个练习展示了应该谨慎选择用 Parallel.Invoke 构造并行执行方法调用的时机。
&O& &判断何时使用 Parallel.Invoke
1.&&& &返回 V isual Studio 2010,在&代码和文本编辑器&窗口中显示 GraphWindow.xaml.cs 文
2.&& 检查 calculateData 方法。 内层 for 循环包含以下语句:
plotXY(data, (int)(-x + (pixelWidth / 2)), (int)(y + (pixelWidth / 2)));
plotXY(data, (int)(x + (pixelWidth / 2)), (int)(y + (pixelWidth / 2)));
这两个语句在 data 数组中设置与两个参数指定的点对应的字节。记住。图表中的点是围绕 X 轴反射的,所以既要为 X 轴的正值调用 plotXY 方法,也要为负值调用。这两个语句看起 来是并行化的两个很好的候选人,因为哪个先运行无关紧要,而且它们设置的是 data 数组 中的不同的字节。
3.&&& &修改这两个语句,把它们包装到一个 Parallel.Invoke 方法调用中,如下所示。注意,两 个调用现在都包装到 lambda 表达式中,第一个 plotXY 调用最后的分号被替换成逗号,第
68 &目前这个版本会使用所有可用的 CPU 内核,而不像上一个使用 Task 对象的版本那样指定 2 个任务就使用 2 个内核。&&译 者注
69 计算限制(compute-bound)是指一个操作要涉及大量计算,任务的执行速度主要受计算速度的限制。下面是计算限制的操 作的一些例子:编译代码、拼写检查、语法检查、电子表格重计算、音频或视频数据转码以及生成图像的缩略图。在金融 和工程应用程序中,计算限制的操作也是十分普遍的。一般应该以异步方式执行一个计算限制的操作,目的是在 GUI 应用 程序中保持 UI 的可响应性,以及用多个 CPU 缩短一个耗时计算所需的时间。&&译者注(摘自《CLR via C#中文版》)
二个 plotXY 调用最后的分号被删除,因为这两个语句现在成了一个参数列表:
Parallel.Invoke(
() =& plotXY(data, (int)(-x + (pixelWidth / 2)), (int)(y + (pixelWidth / 2))), () =& plotXY(data, (int)(x + (pixelWidth / 2)), (int)(y + (pixelWidth / 2)))
4.&& 在&调试&菜单中选择&开始执行(不调试)&来生成并运行应用程序。
5.&& 在 Graph Demo 窗口中单击 Plot Graph 按钮。记录生成图表所花的时间。重复操作几次, 记录平均值。
出乎意料的是,应用程序的运行时间反而变长了。速度比以前慢了最多 20 倍。
6.&& 关闭 Graph Demo 窗口。
之所以变得如此之慢,原因在于 plotXY 方法。这个方法实际是非常简单的:
private void plotXY(byte[] data, int x, int y)
data[x + y * pixelWidth] = 0xFF;
这个方法实是太简单了,瞬间就可以完成,而且绝对算不上是一段计算限制的代码。事实 上,它是如此简单,以至于创建任务、在单独的线程上运行这个任务并等待任务完成,所 产生的开销远远大于直接运行这个方法的开销。方法每次调用时,这些额外的开销可能只 有区区几毫秒,但请注意方法的运行次数;这个方法调用位于一个嵌套的循环中,会执行 几千次,造成累积起来的开销非常大。一般规则是,只有在值得的时候才使用 Parallel.Invoke。 只有涉及密集型计算的操作,才使用 Parallel.Invoke。
如本章前面所述,使用 Parallel 类的另一个重要考虑是操作的独立性。例如,假如为一个循 环使用 Parallel.For,但它的迭代不是独立的(一次迭代要依赖上一次迭代的结果),就会造 成无法预料的结果。下面用一个例子对此进行了演示:
using System.T
using System.Threading.T
namespace ParallelLoop
class Program
private static int accumulator = 0;
static void Main(string[] args)
for (int i = 0; i & 100; i++)
AddToAccumulator(i);
Console.WriteLine(&Accumulator is {0}&, accumulator);
private static void AddToAccumulator(int data)
if ((accumulator % 2) == 0)
accumulator +=
accumulator -=
这个程序遍历 0 到 99 的值,为每个值都调用 AddToAccumulator 方法。AddToAccumulator 方法检查 accumulator 变量的当前值,如果是偶数,就将参数值加到 accumulator 变量上; 否则就从变量中减去参数的值。循环终止后,显示结果。这个应用程序在 ParallelLoop 解决 方案中提供,它在你的&文档&文件夹下的\Microsoft Press\V isual CSharp Step By Step\Chapter
27\ParallelLoop 子文件夹中。运行这个程序,输出结果应该是&100。 为了增大这个简单的应用程序的并行度,一些人会草率地将 Main 方法中的 for 循环替换成 Parallel.For,如下所示:
static void Main(string[] args)
Parallel.For (0, 100, AddToAccumulator); Console.WriteLine(&Accumulator is {0}&, accumulator);
然而,完全没有办法保证创建的各个任务按照一个固定的顺序调用 AddToAccumulator 方法。
(而且代码不是线程安全的,因为多个线程可能尝试同时修改 accumulator &变量。) AddToAccumulator 方法计算的值要取决于计算顺序,所以在进行上述修改之后,应用程序 每次运行都可能生成不同的结果。在这个简单的例子中,你可能看不到计算的值有什么变 化,因为 AddToAccumulator 方法运行得太快,.NET Framework 可能选择用同一个线程顺序 运行每一个调用。然而,如果像以下加粗的部分那样修改 AddToAccumulator 方法,就会得 到不同的结果:
private static void AddToAccumulator(int data)
if ((accumulator % 2) == 0)
accumulator +=
Thread.Sleep(10); // 等待0 毫秒
accumulator -=
Thread.Sleep 方法导致当前线程等待指定的时间。这个修改模拟线程执行其他工作,会影 响.NET Framework 对任务的调度方式。一般的规则是,只有保证循环的每一次迭代都可以 独立地进行,才可以使用 Parallel.For &和 Parallel.ForEach,而且要对代码进行全面测试。 Parallel.Invoke 也要进行类似的考虑;只有方法调用可以独立地进行,而且应用程序不依赖
于它们的执行顺序,才可以使用这个构造。
27.2.6 从任务返回值
到目前为止,你看到的所有 Task 对象的例子都是运行代码来执行一些工作,但不返回一个 值。然而,有时也需要运行一个方法并计算一个结果。TPL 包含了 Task 类的一个泛型变体, 即 Task&TResult&,可以把它用于这个目的。
可以采用与 Task &对象相似的方式创建并运行一个 Task&TResult&对象。主要的区别在于, Task&TResult&对象运行的方法会返回一个值,要将这个返回值的类型指定为 Task 对象的类 型参数 T。例如,下例的 calculateValue 方法要返回一个整数值。为了用一个任务调用该方 法,要创建一个 Task&int&对象,并调用 Start 方法。为了获得方法的返回值,要查询 Task&int& 对象的 Result 属性。如果任务还没有完成它的工作,结果还不可用,Result 属性就会阻塞 调用者。也就是说,你不需要执行任何同步操作,而且知道当 Result 属性返回一个值时, 任务已经完成了它的工作。
Task&int& calculateValueTask = new Task&int&(() =& calculateValue(...));
calculateValueTask.Start(); // 调用calculateValue 方法
int calculatedData = calculateValueTask.R // 阻塞,直到calculateValueTask 完成
private int calculateValue(...)
// 执行计算并填充someValue
return someV
当然,还可以使用 TaskFactory 对象的 StartNew 方法,在同一个步骤中创建一个 Task&TResult& 对象并启动它。下例展示了如何为一个 Task&int&对象使用默认 TaskFactory 来创建并运行一 个调用了 calculateValue 方法的任务:
Task&int& calculateValueTask = Task&int&.Factory.StartNew(() =& calculateValue(...));
为了进一步简化代码(同时运行返回匿名类型的任务),TaskFactory 类提供了 StartNew 方 法的泛型重载版本,它们能推断任务运行的方法所返回的类型。另外,Task&TResult&类是 从 Task 类继承的,所以能像下面这样重写上面的例子:
Task calculateValueTask = Task.Factory.StartNew(() =& calculateValue(...));
下一个练习展示了一个更详细的例子。在这个练习中,将重新构建 GraphDemo 应用程序来 使用一个 Task&TResult&。虽然这个例子看起来比较&学术气&,但在许多现实情况下,都可 以发现这个技术的用处。
&O& &修改 GraphDemo 应用程序来使用一个 Task&TResult&对象
1.&&& &在 V isual &Studio &2010 &中打开 GraphDemo &解决方案,它在你的&文档&文件夹下的
\Microsoft Press\V isual CSharp Step By Step\Chapter 27\GraphDemo Using Tasks that Return
Results 子文件夹中。
这是前面创建一组共 4 个任务的 GraphDemo 应用程序的副本。
2.&&& &在解决方案资源管理器中,展开 GraphDemo 项目中的 GraphWindow.xaml 节点。双击
GraphWindow.xaml.cs 在&代码和文本编辑器&窗口窗口中显示窗体的代码。
3.&&& &找到 plotButton_Click 方法。用户单击窗体上的 Plot Graph 按钮就会运行这个方法。目 前,它是创建一组 Task 对象来执行必要的各个计算,并生成图表所需的数据。然后,它等 待这些 Task 对象完成,并在窗体的 Image 控件中显示结果。
4.&& 在 plotButton_Click 方法后面添加一个名为 getDataForGraph 的新方法。 该方法获取一个名为 dataSize 的整数参数,返回一个字节数组,如下所示:
private byte[] getDataForGraph(int dataSize)
会为这个代码添加代码,以便在字节数组中生成图表数据,并向调用者返回该数组。dataSize
参数指定了数组大小。
5.&& 将创建 data 数组的语句从 plotButton_Click 方法移到 getDataForGraph 方法,如加粗的 语句所示:
private byte[] getDataForGraph(int dataSize)
byte[] data = new byte[dataSize];
6.&&& &将创建、运行和等待 Task &对象(这些 Task &对象对 data &数组进行填充)的代码从 plotButton_Click 方法移至 getDataForGraph 方法,在方法末尾添加一个 return 语句,将 data 数组传回给调用者。
完整的 getDataForGraph 方法如下所示:
private byte[] getDataForGraph(int dataSize)
byte[] data = new byte[dataSize];
Task first = Task.Factory.StartNew(() =& generateGraphData(data, 0, pixelWidth / 8)); Task second = Task.Factory.StartNew(() =& generateGraphData(data, pixelWidth / 8,
pixelWidth / 4));
Task third = Task.Factory.StartNew(() =& generateGraphData(data, pixelWidth / 4, pixelWidth * 3 / 8));
Task fourth = Task.Factory.StartNew(() =& generateGraphData(data, pixelWidth * 3 / 8, pixelWidth / 2));
Task.WaitAll(first, second, third, fourth);
提示:可将创建任务并等待它们完成的代码替换成以下 Parallel.Invoke 构造:
Parallel.Invoke(
() =& Task.Factory.StartNew(() =& generateGraphData(data, 0, pixelWidth / 8)) () =& Task.Factory.StartNew(() =& generateGraphData(data, pixelWidth / 8,
pixelWidth / 4)),
() =& Task.Factory.StartNew(() =& generateGraphData(data, pixelWidth / 4, pixelWidth * 3 / 8)),
() =& Task.Factory.StartNew(() =& generateGraphData(data, pixelWidth * 3 / 8,
pixelWidth / 2))
7.&& 在 plotButton_Click 方法中,在创建 Stopwatch 变量(用于对任务进行计时)的语句后, 添加以下加粗的语句,它创建一个名为 getDataTask 的 Task&byte[]&对象,并用这个对象运 行 getDataForGraph 方法。方法返回一个字节数组,所以任务的类型是 Task&byte []&。StartNew 方法调用引用了一个 lambda 表达式,后者调用 getDataForGraph 方法,并传递 dataSize 变 量作为参数。
private void plotButton_Click(object sender, RoutedEventArgs e)
Stopwatch watch = Stopwatch.StartNew();
Task&byte[]& getDataTask = Task&byte[]&.Factory.StartNew(() =& getDataForGraph(dataSize));
8.&&& &创建并启动了 Task&byte &[]&对象后,添加以下加粗显示的语句检查 Result &属性,将 getDataForGraph 方法返回的 data 数组赋给局部字节数组变量 data。记住,Result 属性会阻 塞调用者,直到任务完成时为止。所以,不需要显式地等待任务完成。
private void plotButton_Click(object sender, RoutedEventArgs e)
Task&byte[]& getDataTask = Task&byte[]&.Factory.StartNew(() =& getDataForGraph(dataSize));
byte[] data = getDataTask.R
注意:创建一个任务,然后等它完成才继续做其他事情,这看起来似乎很奇怪,因为它似
乎唯一的目的就是为程序增加开销。然而,下一节将解释为什么要采取这种方式。
9.&& 验证 plotButton_Click 方法最终的代码如下:
private void plotButton_Click(object sender, RoutedEventArgs e)
if (graphBitmap == null)
graphBitmap = new WriteableBitmap(pixelWidth, pixelHeight, dpiX, dpiY, PixelFormats.Gray8, null);
int bytesPerPixel = (graphBitmap.Format.BitsPerPixel + 7) / 8;
int stride = bytesPerPixel * pixelW
int dataSize = stride * pixelH
Stopwatch watch = Stopwatch.StartNew();
Task&byte[]& getDataTask = Task&byte[]&.Factory.StartNew(() =& getDataForGraph(dataSize));
byte[] data = getDataTask.R
duration.Content = string.Format(&Duration (ms): {0}&, watch.ElapsedMilliseconds); graphBitmap.WritePixels(new Int32Rect(0, 0, pixelWidth, pixelHeight), data, stride, 0); graphImage.Source = graphB
10.& &在&调试&菜单中选择&开始执行(不调试)&来生成并运行应用程序。
11.& &在 Graph Demo 窗口中,单击 Plot Graph。验证图表会和往常一样生成,而且所花的时 间和前面差不多。(报告的时间可能稍微长一些,因为 data 数组现在是由任务创建的。而 在以前,它是在任务开始运行前创建的。)
12.& &关闭 Graph Demo 窗口。
-----------

我要回帖

更多关于 爱护环境的一段话 的文章

 

随机推荐