【进阶修炼】——改善C#程序质量(5)

71, 区分异步和多线程的应用场景。

计算机的很多硬件,如硬盘,光驱,声卡,网卡都有DMA(Direct Memory Access)功能,它可以不占用cpu的资源,而异步的提出恰恰就是基于这个的。而多线程是操作系统上的并行执行的代码,是会占用cpu资源的。所以关于这两种的使用场景建议是:1)对于I/0密集型操作使用异步。2)对于计算密集型操作使用多线程。

72, 在线程同步中使用信号量。

值类型是不能被锁定的,引用类型上的等待机制,分为锁定和信号同步。锁定通过lock关键字或Monitor来完成;信号同步用AutoResetEvent,ManualResetEvent,Semaphore,Mutex,其中Mutex可以在不同进程间同步。

73, 避免锁定不恰当的对象。

同步对象在需要同步的多个线程中是可见的同一个对象,不要锁定字符串,类型本身,实例本身。降低同步对象的可见性,尽量让它变为private的。

74, 警惕线程的IsBackground。

用Thread类创建的线程是前台线程,如果前台线程不全部退出,应用程序就关不掉。而ThreaadPool里的线程默认是后台的。所以一些关键性的操作才需要前台线程,否则应将IsBackground设为true。

75, 警惕线程不会立即启动。

这取决于操作系统的调度,我们不能假设它会立即启动。

76, 警惕线程的优先级。

Thread和ThreadPool创建的线程优先级都是Nomal的,我们一般没有必要去修改这个优先级,对于Window操作系统,优先级越高在调度时会得到优先执行。

77, 正确停止线程。

1)正如线程不能立即启动一样,线程的停止也不是想停就能停,他必须做完必要的工作后才能停止,以传统的Thread.Abort方法为例,调用这个方法后,如果代码在和非托管代码打交道,只有等到这部分代码结束后才能引发ThreadAbortException,即便是在CLR环境中ThreadAbortException也不会立即引发。2)要正确停止线程,不在于调用者采取的行为(Abort),而更多的依赖于线程能否主动响应调用者的停止请求。线程应该开放一个Cancled接口,工作线程以某种频率检测Cancled标识,如果发生改变,线程自己负责退出。.net提供了一个CancellationTokenSource用于协作取消。cts.Token.IsCancellationRequested判断是否有取消操作,cts.Cancel()发送取消请求,线程结束时的如需回调,可以用cts.Token.Register(() => {// 回调 })注册一个回调函数;

78, 应避免线程数量过多。

对于Socket类型的操作应该用异步,而不要开线程。

79, 使用ThreadPool或BackgroundWorker替代Thread。

ThreadPool线程池可以重复利用空闲的线程。BackgroundWorker可以支持进度,完成通知,取消功能,内部也是用的线程池。

80, 用Task来替代ThreadPool。

Task在线程池的基础上进行了优化,Task提供了ContinueWith方法来提供后续任务的执行。Task提供了IsCanceled,IsCompleted,IsFaulted这几个属性用于查询任务的执行结果。并且还支持取消功能。Task的Result属性用于查询函数的返回结果,如果执行过程被取消,调用这个属性会引发AggregateException异常,Task比ThreadPool功能丰富了很多,应尽量多用Task。

81, 使用Parallel简化同步状态下的Task使用。

Parallel是一个静态类,提供了For,Foreach,Invoke方法启动多个Task执行任务。

82, Parallel简化但不等同于Task的默认行为。

Parallel的方法调用后是阻塞的,尽管他内部的Task是并行计算的,除非这些Task都执行完了,Parallel后面的代码才会得到执行。

83, 小心Parallel的陷阱。

Parallel的For方法还提供了线程开始前和结束后执行的方法。但由于Task使用的线程池,比如4次循环可能使用了1个或2个线程,这会带来这两个方法执行次数的不确定性。另外,toExclusive参数指定值为5的时候,循环结束的位置是它的前一个值4,而不是5。

84, 使用PLINQ。

PLINQ即并发执行的LINQ,由于是并发,对于大数据量的查询,它的效率可能会更好。要使用PLINQ,只用调用数据源的扩展方法AsParallel方法,在ForEach查询中,结果就会得到并行计算,当然输出是无序的。如果要按有序输出,可以在数据源上调用AsOrderd方法,如:arr.AsParallel().AsOrdered()。注意在查询的ForAll方法中,输出始终是无序的。关于PLINQ的使用,应该进行充分的学习了解后再使用,否则可能会带来意想不到的bug。

85, Task中的异常处理。

调用任务的Wait,WaitAny,WaitAll方法,或查询Result属性,如果Task发生了异常,会生成一个AggregateException,我们通过循环查询它的InnerException就可以知道所有的异常。但是Wait等方法是会阻塞主线程的。Task产生的异常,如果在Task内部没有得到处理,也没有抛给主线程的话,那么只有垃圾回收的时候才会触发TaskScheduler.UnobservedTaskException事件,如果调用了UnobservedTaskException的e.SetObserved();方法,异常就不会再往上传递,否则还会进一步触发CurrentDomain_UnhandledException事件,程序终止。所以,Task内部的异常应该立即得到处理。简单一点的做法是Task内部逻辑对捕获的异常进行处理,比如写日志之类的。另一种是,在外部定义一个事件,Task内部出现异常时,通过这个事件通知给外部,在这里进行异常的后续操作,这种方法比较可取。

86, Parallel中的异常处理。

Parallel中的异常和Task中的异常不一样。它可以抛出给主线程。但是我们也可以在内部声明一个AggregateExcption,把所有的异常捕获后,打包到AggregateExcption里面,一起抛出,个人感觉意义不大。

87, 区分Winform和WPF的线程模型。

Winform中是通过ISynchronizeInvoke接口的InvokeRequire属性来判断控件是否处在其他线程中。Winform中的所有控件都继承了这个接口。WPF中是通过DispatcherObject提供的CheckAccess和VerifyAccess方法来判断,其中VerifyAccess判断不是同一个线程会抛出异常。

88, 并行并不总是速度最快。

因为并行会带来额外的线程开销,对于小的循环体,计算量不大,并行反而会更慢。

89, 在并行方法中谨慎使用锁。

并行中有多个线程,加锁会带来线程的切换,影响性能。可以适当考虑用Interlock对整型进行原子加减操作而避免线程切换的开销。如果整个代码都是需要加锁的,那么实际上就没有并行的意义,用同步的方式或许会更好。

时间: 2024-10-02 00:33:42

【进阶修炼】——改善C#程序质量(5)的相关文章

【进阶修炼】——改善C#程序质量(1)

这是一个大纲形式的概要,以便自己可以花较少的时间反复阅读.在开发中,多加注意这些有用的建议,让自己成为一个更优秀的程序员.内容主要来自<编写高质量代码-改善C#程序的157个建议>(陆敏技),这本书写的真的很好,都是些实战经验的总结,建议大家购买,这其中的建议不仅仅适合于C#,只要你做.NET开发,阅读此书都会从中受益.同时,其他书籍和资料的一些好的编程建议,我也会不断更新到这里. 1, 字符串使用. 应避免发生装箱:避免分配额外的内存:考虑使用StringBuilder来替代string.s

【进阶修炼】&mdash;&mdash;改善C#程序质量(9)

140,使用默认的访问修饰符. 如果不加访问修饰符,成员变量的默认是private的,类默认是internal的.为了明确访问的权限,我倒是建议都加上访问修饰符,这省不了多少代码. 141,不知道该不该加大括号的时候就加上. 大括号会多占用两行代码,到底一行语句的代码需不需要加大括号这是一个争论.但是为了避免引入不必要的bug还是加上吧. 142,总是提供有意义的命名. 我们不希望看到如iTemp的命名,类似于i的命名只应该出现在循环中.变量的命名应尽可能表达它应该表达的意思,不要太随意的命名,

【进阶修炼】&mdash;&mdash;改善C#程序质量(2)

16, 元素可变的情况下应避免用数组. 数组是定长的集合,可以考虑用ArrayList或List<T>集合.ArrayList元素是object类型,有装箱的开销,性能较低.另外Array类提供了Array.CreateInstance来创建数组,Array.Copy来拷贝数组,但这牵涉到新数组的创建,会增加开销. 17, 多数情况下用foreach代替for循环. 18, Foreach不能代替for. Foreach不能对元素进行增删操作,不能访问序号. 19, 使用对象初始化器和集合初始

【进阶修炼】&mdash;&mdash;改善C#程序质量(7)

113,声明变量时考虑最大值. Ushort的最大值是65535,用于不同的用途这个变量可能发生溢出,所以设计时应充分了解每个变量的最大值. 114,MD5不再安全. MD5多用于信息完整性的校验.R=H(S),MD5的算法是不可逆的,也就是我们几乎没有可能根据生产的MD5码去还原原文.但是我们可以使用穷举的办法生成MD5码来对比,由于我们平常设定的密码都比较简单,如:123456,根本不用很长的时间就会被破解掉.为了有效防止破解,一个可行的做法是为我们的密码固定加一个字符串"%¥#--@!&a

【进阶修炼】&mdash;&mdash;改善C#程序质量(3)

32, 总是优先考虑泛型. 泛型代码有很好的重复利用性,和类型安全性. 33, 应尽量避免在泛型类中声明静态成员. 静态成员达不到共享的目的.List<int>和List<String>是两个不同的类型,而静态成员是针对类型的.当然2个List<int>之间是可以共享静态成员的,但为了不必要的混淆,应该避免使用静态成员. 34, 为泛型参数添加约束. 没有约束的参数,功能是有限的,添加了约束后,我们就可以使用约束类型的方法和属性了,程序更加灵活. 35, 使用Defau

【进阶修炼】&mdash;&mdash;改善C#程序质量(6)

90,不应为抽象类指定public的构造函数. 抽象类即使指定了public的构造函数,也是不能实例化的,编译通不过.抽象类的构造函数应该设定为protected,它的作用应该是初始化自己的成员,以及可以被子类构造函数调用.设定为public权限毫无意义. 91,可见字段应该重构为属性. 属性比字段有更好的灵活性,可以加入代码控制,可以在类型内部实现线程安全访问,可以让类型得到通知(如WPF系统),这些都是字段所不具备的.当然,如果仅限于类型内部使用,应该用字段. 92,谨慎将数组或集合作为属性

【进阶修炼】&mdash;&mdash;改善C#程序质量(4)

46, 显示释放资源,需要实现IDisposable接口. 最好按照微软建议的Dispose模式实现.实现了IDisposable接口后,在Using代码块中,垃圾会得到自动清理. 47, 即使提供了显示的释放方法,也应该在终结器中提供隐式实现. 因为我们不能保证用户会主动去调用这个释放方法,但我们要保证在垃圾回收时,这些资源能得到清理. 48, Dispose方法应该允许被多次调用. 我们可以创建一个变量disposed来标明对象是否被释放了. 49, 在Dispose模式中应提供一个受保护的

【进阶修炼】&mdash;&mdash;改善C#程序性能(1)

这是一个大纲形式的提点,以便自己可以花较少的时间时常浏览.在开发中,多加注意这些有用的建议,让自己成为一个更优秀的程序员.内容主要来自<编写高质量代码-改善C#程序的157个建议>(陆敏技),这本书写的真的很好,都是些实战经验的总结,建议大家购买此书,这其中的建议不光适合于C#,只要你做.NET开发,阅读此书都会从中受益.同时,如其他书籍和资料有好的建议,我也会不断更新到这里. 1, 字符串使用. 应避免发生装箱:避免分配额外的内存:考虑使用StringBuilder来替代string.str

Android 控件进阶修炼-仿360手机卫士波浪球进度控件

技术:Android+java 概述 像360卫士的波浪球进度的效果,一般最常用的方法就是 画线的方式,先绘sin线或贝塞尔曲线,然后从左到右绘制竖线,然后再裁剪圆区域. 今天我这用图片bitmap的方式,大概的方法原理是: (1)首先用clipPath裁剪园区域, (2)然后用4张图来不断绘制到画布上,再用偏移量来控制移动的速度,从而形成波浪动态效果. (3)有一点需要注意的是,裁剪圆的时候用到的clipPath这个方法,在android 4.1,和4.2等某些系统上,裁剪出来不是圆,而是矩形