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对整型进行原子加减操作而避免线程切换的开销。如果整个代码都是需要加锁的,那么实际上就没有并行的意义,用同步的方式或许会更好。

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

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

    这是一个大纲形式的概要,以便自己可以花较少的时间反复阅读.在开发中,多加注意这些有用的建议,让自己成为一个更优秀的程序员.内容主要来自<编写高质量代码-改善C#程序的157个建议>(陆敏技 ...

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

    158,不要写冗余注释. 注释应该写代码没有表达的东西. 代码能够自我描述就不要加注释. 159,废弃的注释应该尽早删除. 废弃的注释由于年代太久远,已经和现在的代码逻辑不匹配了,这样的注释只会误导人 ...

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

    140,使用默认的访问修饰符. 如果不加访问修饰符,成员变量的默认是private的,类默认是internal的.为了明确访问的权限,我倒是建议都加上访问修饰符,这省不了多少代码. 141,不知道该不 ...

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

    122,以<Company>.<Component>作为命名空间. 如Microsoft.Windows.Design.也可以用域名作为空间,如www.microsoft.co ...

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

    113,声明变量时考虑最大值. Ushort的最大值是65535,用于不同的用途这个变量可能发生溢出,所以设计时应充分了解每个变量的最大值. 114,MD5不再安全. MD5多用于信息完整性的校验.R ...

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

    90,不应为抽象类指定public的构造函数. 抽象类即使指定了public的构造函数,也是不能实例化的,编译通不过.抽象类的构造函数应该设定为protected,它的作用应该是初始化自己的成员,以及 ...

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

    46, 显示释放资源,需要实现IDisposable接口. 最好按照微软建议的Dispose模式实现.实现了IDisposable接口后,在Using代码块中,垃圾会得到自动清理. 47, 即使提供了 ...

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

    32, 总是优先考虑泛型. 泛型代码有很好的重复利用性,和类型安全性. 33, 应尽量避免在泛型类中声明静态成员. 静态成员达不到共享的目的.List<int>和List<Strin ...

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

    16, 元素可变的情况下应避免用数组. 数组是定长的集合,可以考虑用ArrayList或List<T>集合.ArrayList元素是object类型,有装箱的开销,性能较低.另外Array ...

随机推荐

  1. stm32调试过程中如何判断是程序问题还是硬件问题?

    可以用软件仿真,如果软件仿真时寄存器什么的都正确,而硬件在线仿真时不对,则说明是板子硬件的问题

  2. [转]Java加密算法

    如基本的单向加密算法: BASE64 严格地说,属于编码格式,而非加密算法 MD5(Message Digest algorithm 5,信息摘要算法) SHA(Secure Hash Algorit ...

  3. Hive 打开调试模式

    编程过程中发现错误,无法看到具体的错误信息,网上搜了下,按照下面的方法启动Hive Shell就可以打开调试(注意:此方法只对本次回话有效): hive --hiveconf hive.root.lo ...

  4. python3 装饰器应用举例

    [引子] python 中的装饰器是oop(面向对象编程)设计模式.之装饰器模式的一个应用.由于有语法糖衣的缘故.所以写起来也更加方便 [从一个比较经典的应用场景来讲解装饰器] 有过一定编程经历的工程 ...

  5. MySQL存储过程相互调用

    什么都不说了上代码: 方式一: 第一个存储过程:test1,参数如下:IN `user_name` VARCHAR(50),OUT `uid`  bigint(20) BEGIN #Routine b ...

  6. mysql 主从同步遇到的问题(1032)

    event_scheduler对主从的影响: 1 对于已经存在的主从, 新建立events没有影响. 2 对于新建立的主从,如果有events ,那么需要在从库上把event_scheduler设置为 ...

  7. iOS 基础-----关于UIView 的 frame 与 bounds

    首先,对于frame 大家都很熟悉,是当前view ,相对于其父视图view 的坐标,例如: UIView *view1 = [[UIView alloc] initWithFrame:CGRectM ...

  8. 【Unity】3.1 利用内置的3D对象创建三维模型

    分类:Unity.C#.VS2015 创建日期:2016-04-02 一.基本概念 Unity已经内置了一些基本的3D对象,利用这些内置的3D对象就可以直接构建出各种3D模型(当然,复杂的三维模型还需 ...

  9. lua--clone

    clone 深度克隆一个值. 格式: value = clone(值) 用法示例: -- 下面的代码,t2 是 t1 的引用,修改 t2 的属性时,t1 的内容也会发生变化 , b = } local ...

  10. numpy数组-标准化数据

    标准化数据的公式: (数据值 - 平均数) / 标准差 import numpy as np employment = np.array([ 55.70000076, 51.40000153, 50. ...