C#学习笔记14
1.在多个线程的同步数据中,避免使用this、typeof(type)、string进行同步锁,使用这3个容易造成死锁。
2.使用Interlocked类:我们一般使用的互斥锁定模式(同步数据)为Lock关键字(即Monitor类),这个同步属于代价非常高的一种操作。除了使用Monitor之外,还有一个备选方案,它通常直接由处理器支持,而且面向特定的同步模式。Interlocked类中包含一些常用方法,如CompareExchange、Decrement、Increment、Exchange。这些都是针对单个的值(对象)进行同步数据处理。
3.多个线程时的事件通知:可以查看Utility.EventInThread()代码清单。
4.同步设计的最佳实践:
(1)避免死锁;如两个线程都等待对方锁定的资源释放,线程A锁定sync1资源,线程B锁定sync2资源,线程A请求锁定sync2资源,线程B请求锁定sync1资源,此时便出现死锁。死锁的发生必须满足4个基本条件。互斥、占有并等待、不可抢先、循环等待条件。
(2)何时提供同步;通常针对静态数据进行同步,并有公共方法来修改数据,方法内部应处理好同步问题。
(3)避免不必要的锁定。
5.更多同步类型:System.Threading.Mutex类在概念上与Monitor类一致,只是其是为支持进程之间的同步。如同步对文件或其他跨进程资源的访问,限制程序只能运行一个实例。如Utility.UseMutex()代码清单。Mutex类派生自WaitHandle,可以自动获取多个锁(这是Monitor类所不支持的)。
6.WaitHandle类:多个同步类是继承于它,如Mutex、EventWaitHandle、Semaphore,WaitHandle类的关键方法为WaitOne(),它有多个重载版本,这些方法会阻塞当前线程,直到WaitHandle实例收到信号或被设置(调用Set())。
7.重置事件类:重置事件与C#中委托以及事件没有任何关系,用于多线程的控制,重置事件用于强迫代码等候另一个线程的执行,直到获得事件已发生的通知。重置事件类有ManualResetEvent、ManualResetEventSlim(.net4.0新增,针对前者进行优化)、AutoResetEvent(主要使用前面2个类型),它们提供的关键方法为Set()与Wait()。调用Wait()方法会阻塞一个线程的执行,直到一个不同的线程调用Set(),或者设定的等待时间结束,才会继续运行。可查看Utility.UseManualResetEvent()代码清单。
8.并发集合类:.net4.0新增了一些类是并发集合类,这些类专门设计用来包含内建的同步代码,使它们能支持多个线程访问而不必关心竞态条件。如BlockingCollection<T>、ConcurrentBag<T>、ConcurrentDictionary<K,V>、ConcurrentQueue<T>、ConcurrentStack<T>,利用并发集合,可以实现的一个常见的模式是生产者和消费者的线程安全的访问。
9.线程本地存储:同步的一个替代方案是隔离,而实现隔离的一个办法是使用线程本地存储,利用线程本地存储,线程就有了专属的变量实例。线程本地存储实现有2中方式,分别为ThreadLocal<T>和ThreadStaticAttribute类,其中ThreadLocal<T>类是.net4.0新增的。可查看LocalVarThread类的代码清单。
10.计时器:有三种计时器分别为System.Windows.Forms.Timer、System.Timers.Timer、System.Threading.Timer,Forms.Timer用于用户界面编程,能够安全的访问用户界面上的窗体与控件,Timers.Timer是Threading.Timer的包装器,是对其功能的抽象(System.Threading.Timer类型轻量一些)。
功能描述 |
System.Timers.Timer |
System.Threading.Timer |
System.Window.Forms.Timer |
支持在计时器实例化之后添加和删除侦听器 |
是 |
否 |
是 |
支持用户界面线程上的回调 |
是 |
否 |
是 |
从自线程池获取的线程进行回调 |
是 |
是 |
否 |
支持在Windows窗体设计器中拖放 |
是 |
否 |
是 |
适合在一个多线程服务器环境中运行 |
是 |
是 |
否 |
支持将任意状态从计时器初始化传递至回调 |
否 |
是 |
否 |
实现Idisposable |
是 |
是 |
是 |
支持开关式回调和定期重复回调 |
是 |
是 |
是 |
可穿越应用程序域的边界访问 |
是 |
是 |
是 |
支持Icomponent,可容纳在一个Icontainer中 |
是 |
否 |
是 |
11.异步编程模型(Async Program Model,APM):异步编程是多线程的一种方式,APM的关键在于成对使用BeginX和EndX方法(X一般对应同步版本的方法名),而且这些方法具有完善的签名。BeginX返回一个System.IAsyncResult对象,可通过它访问异步调用的状态,以便等待完成或轮询完成。然而EndX方法获取这个返回的对象作为输入参数。这样才真正将两个方法配成一对,让我们可以清晰地判断哪个BeginX方法调用和哪个EndX方法调用配对。APM的本质要求所有BeginX调用都必须有一个(而且只能有一个)EndX调用。因此,不可能发生两个EndX调用接受同一个IAsyncResult实例的情况。我们还可以使用IAsyncResult的WaitHandle判断异步方法何时结束,IAsyncResult的WaitHandle是在回调执行之前进行通知完成。
EndX方法具有4个方面的用途。
首先,调用EndX会阻塞线程继续执行,直到请求的工作成功完成(或者发生错误并引发异常)。
其次,如果方法X要返回数据,这个数据可从EndX方法调用中访问。
再次,如果执行请求的工作时发生异常,可在调用EndX时重新引发这个异常,确保异常会被调用代码发现——好像它是在一次同步调用上发生的那样。
最后,如果任何资源需要在调用X后清理,EndX将负责清理这些资源。
BeginX方法有两个额外的参数,在同步版本的方法中是没有的,一个是回调参数,是方法结束时要调用的一个System.AsyncCallback委托,另一个是object类型的状态参数(State)。在使用回调时,可以把EndX放在其内部执行。
12.使用TPL(任务并行库)调用APM:虽然TPL大幅简化了长时间运行方法的异步调用,但通常最好是使用API提供的APM方法,而不是针对同步版本编写TPL。这是因为API开发人员知道如何编写最高效率的线程处理代码,知道同步哪些数据以及要使用什么同步类型。TPL包含FromAsync的一组重载版本,用于调用APM。
13.异步委托调用:有一个派生的APM模式,称为异步委托调用,它在所有委托数据类型上使用了特殊的、由C#编译器生成的代码。例如,给定Func<string,int>的一个委托实例,可以在这个实例上使用以下APM方法对。
System.IAsyncResult BeginInvoke(string arg,AsyncCallback callback,object obj)
Int EndInvode(IasyncResult result)
结果是可以使用C#编译器生成的方法来同步地调用任何委托(进而调用任何方法)。遗憾的是,异步委托调用模式使用的基础技术是一种不再继续开发的分布式编程技术,称为远程处理。虽然微软仍然支持异步委托调用,而且在可以预见的将来,也不会放弃对它的支持,但和其他技术相比,它的性能显得比较一般。其他技术包括Thread、ThreadPool和TPL等。因此,在开发新项目时,开发人员应尽量选用其他技术,而不要使用异步委托调用API。在TPL之前,异步委托调用模式比其他替代方案容易得多,所以假如一个API没有提供显式的异步调用模式,一般都会选用它。然而,在TPL问世之后,除非是为了与.Net3.5和早期框架版本兼容,否则异步委托调用越来越没有什么用了。
14.基于事件的异步模式(EAP):比APM更高级的一种编程模式是基于事件的异步模式。和APM一样,API开发人员为长时间运行的方法实现了EAP。其中Background Worker模式,它是EAP的一个特定的实现。
15.Background Worker模式:建立Background Worker模式的过程如下。
(1)为BackgroundWorker.DoWork事件注册长时间运行的方法。
(2)为了接受进展或状态通知,要为BackgroundWorker.ProgressChanged挂接一个侦听器,并将BackgroundWorker.WorkerReportsProgress设为true。
(3)为BackgroundWorker.RunWorkerCompleted事件注册一个方法。
(4)为WorkerSupportsCancellation属性赋值以支持取消一个操作。将true值赋给该属性以后,对BackgroundWorker.CancelAsync的调用就会设置DoWorkEventArgs.CancellationPending标志。
(5)在DoWork提供的方法内,检查DoWorkEventArgs.CancellationPending属性值,并在它为true时退出方法。
(6)一切都设置好之后,调用BackgroundWorker.RunWorkerAsync(),并提供要传给指定DoWork()方法的一个状态参数来开始工作。
分解成以上小步骤以后,Background Worker模式就显得容易理解。另外,由于它本质上是一种EAP,所以提供了对进度通知的显式支持。后台的工作者(worker)线程异步执行的时候,假如发生一个未处理的异常,RunWorkerCompleted委托的RunWorkerCompletedEventArgs.Error属性就会设置成Exception实例。因此,我们通过在RunWorkerCompleted回调内检查Error属性来提供异常处理机制。
16.Windows UI编程:使用System.Windows.Forms和System.Windows命名空间来进行用户界面开发时,也必须注意线程处理问题。Microsoft Windows系列操作系统使用的是一个单线程的、基于消息处理的用户界面。这意味着,每次只能有一个线程访问用户界面,与轮换线程的任何交互都应该通过Windows消息泵来封送。
17.Windows窗体:进行Windows窗体编程时,为了检查是否允许从一个线程中发出UI调用,需要调用一个组件的InvokeRequired属性,判断是否需要进行封送处理。如果InvokeRequired返回true,表明需要封送,并可通过一个Invoke()调用来实现。尽管Invoke()在内部无论如何都会检查InvokeRequired,但更有效率的做法是提前显示地检查这个属性。由于封送到另一个线程可能是相当慢的一个过程,所以可以通过BeginInvoke()和EndInvoke()来进行异步调用。Invoke()、BeginInvoke()、EndInvoke()和InvokeRequired构成了System.ComponentModel.ISynchronizeInvoke接口的成员。该接口已由System.Windows.Forms.Control实现,所有Windows窗体控件都是从这个Control类派生。
18.Windows Presentation Foundation(WPF):为了在WPF平台上实现相同的封送检查,需要采取稍有不同的一种方式。WPF包含一个名为Current的静态成员属性,它的类型是DispatcherObject,由System.Windows.Application类提供。在调度器(dispatcher)对象上调用CheckAccess(),作用等同于在Windows窗体中的控件上调用InvokeRequired,然后再使用Application.Current.Dispatcher.Invoke()方法封送。
19.说明:除了TPL提供的模式,还有这么多额外的模式可供选用,这造成许多人不知道应该如何选择。一般情况下,最好是选择由API提供的模式(比如APM或EAP),最后选择TPL模式。
public class Utility
{
private static ManualResetEventSlim firstEvent, secendEvent;
private static object _data; public static void Initialize(object newValue)
{
Interlocked.CompareExchange(ref _data, newValue, null);
} public static void EventInThread()
{
//不是线程安全,在检查委托对象与调用委托之间,存在其他线程对OnTemparatureChange进行赋值操作,可能会被设置为null。
/*if (OnTemparatureChange != null)
{
//调用订阅者
OnTemparatureChange(this, new TemparatureEventArgs());
}*/ //线程安全操作,创建一个委托变量副本,检查副本的null,再触发副本。这样即使OnTemparatureChange委托变量在其他线程中被null化,也不影响。
/*TemparatureChangedHandle localChanged = OnTemparatureChange;
if (localChanged != null)
{
//调用订阅者
localChanged(this, new TemparatureEventArgs());
}*/
} public static void UseMutex()
{
bool firstApplicationInstance;
string mutexName = Assembly.GetEntryAssembly().FullName;
using (Mutex mutex = new Mutex(false, mutexName, out firstApplicationInstance))
{
if (!firstApplicationInstance)
{
Console.WriteLine("This Application is already running.");
}
else
{
Console.WriteLine("Enter to shutdown.");
Console.ReadLine();
}
}
} public static void UseManualResetEvent()
{
using (firstEvent = new ManualResetEventSlim())
using (secendEvent = new ManualResetEventSlim())
{
Console.WriteLine("App start");
Console.WriteLine("start task");
Task task = Task.Factory.StartNew(() =>
{
Console.WriteLine("DoWork start");
Thread.Sleep();
firstEvent.Set();
secendEvent.Wait();
Console.WriteLine("DoWork end");
});
firstEvent.Wait();
Console.WriteLine("Thread executing");
secendEvent.Set();
task.Wait();
Console.WriteLine("Thread completed");
Console.WriteLine("App shutdown");
}
}
} public class LocalVarThread
{
public static ThreadLocal<double> _count = new ThreadLocal<double>(() => 0.01134); public static double Count
{
set { _count.Value = value; }
get { return _count.Value; }
} public static void DoWork()
{
Task.Factory.StartNew(Decrement);
for (int i = ; i < short.MaxValue; i++)
{
Count++;
}
Console.WriteLine("DoWork Count = {0}", Count);
} public static void Decrement()
{
Count = -Count;
for (int i = ; i < short.MaxValue; i++)
{
Count--;
}
Console.WriteLine("Decrement Count = {0}", Count);
}
}
---------------------以上内容根据《C#本质论 第三版》进行整理
C#学习笔记14的更多相关文章
- Ext.Net学习笔记14:Ext.Net GridPanel Grouping用法
Ext.Net学习笔记14:Ext.Net GridPanel Grouping用法 Ext.Net GridPanel可以进行Group操作,例如: 如何启用Grouping功能呢?只需要在Grid ...
- SQL反模式学习笔记14 关于Null值的使用
目标:辨别并使用Null值 反模式:将Null值作为普通的值,反之亦然 1.在表达式中使用Null: Null值与空字符串是不一样的,Null值参与任何的加.减.乘.除等其他运算,结果都是Null: ...
- golang学习笔记14 golang substring 截取字符串
golang学习笔记14 golang substring 截取字符串golang 没有java那样的substring函数,但支持直接根据 index 截取字符串mystr := "hel ...
- mybatis学习笔记(14)-查询缓存之中的一个级缓存
mybatis学习笔记(14)-查询缓存之中的一个级缓存 标签: mybatis mybatis学习笔记14-查询缓存之中的一个级缓存 查询缓存 一级缓存 一级缓存工作原理 一级缓存測试 一级缓存应用 ...
- Python3+Selenium3+webdriver学习笔记14(等待判断 鼠标事件 )
!/usr/bin/env python -*- coding:utf-8 -*-'''Selenium3+webdriver学习笔记14(等待判断 鼠标事件 )'''from selenium im ...
- 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理
1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...
- 【转】 C#学习笔记14——Trace、Debug和TraceSource的使用以及日志设计
[转] C#学习笔记14——Trace.Debug和TraceSource的使用以及日志设计 Trace.Debug和TraceSource的使用以及日志设计 .NET Framework 命名空 ...
- [C++学习笔记14]动态创建对象(定义静态方法实现在map查找具体类名对应的创建函数,并返回函数指针,map真是一个万能类)good
[C++学习笔记14]动态创建对象 C#/Java中的反射机制 动态获取类型信息(方法与属性) 动态创建对象 动态调用对象的方法 动态操作对象的属性 前提:需要给每个类添加元数据 动态创建对象 实 ...
- Linux学习笔记14——使用fcntl实现文件锁定
期末考试快要来了,Linux学习进度一下拉下来许多.今天学习的是文件锁定,在Linux中,实现文件锁定的方法很多,例如fcntl和lockf.下面主要是fcntl的调用. fcntl函数的原型是:in ...
- 批处理学习笔记14 - 把所有.mp4文件全部拷贝进统一目录
今天下载了一套视频教程,结果发现不在同一个目录下,很乱.都放在不同文件夹下. 于是写了一个批处理来解决这个问题 @echo off for /r %%i in (*mp4) do ( copy %%i ...
随机推荐
- VMware安装linux系统报错:已将该虚拟机配置为使用 64 位客户机操作系统。但是,无法执行 64 位操作。
检测问题所在: 下载LeoMoon CPU-V 检查一下CPU VT-x状态是否启用 地址:http://download.csdn.net/detail/qq_22860341/9858011 如果 ...
- 洛谷P4337 [ZJOI2018]线图(状压+搜索+乱搞)
题面 传送门 题解 妈呀调了我整整一天-- 题解太长了不写了可以去看\(shadowice\)巨巨的 //minamoto #include<bits/stdc++.h> #define ...
- SSO单点登录入门
1,SSO简介 SSO(Single Sign-On,单点登录)是身份管理中的一部分.SSO 的一种较为通俗的定义是:SSO 是指访问同一服务器不同应用中的受保护资源的同一用户,只需要登录一次,即通过 ...
- [ActionSprit 3.0] FMS客户端调用服务器端命令或方法
有时候客户端需要和服务器端进行通信,服务器端会有个main.asc文件(当然,文件名可以自己定义),这个就是服务器端程序,是在服务器上执行的,你可以用trace调试,trace的内容会在管理服务器的页 ...
- iOS 设备定位功能可用的判断
if ([CLLocationManager locationServicesEnabled] && ([CLLocationManager authorizationStatus] ...
- iOS 轻松使用 App 数据统计
想获取用户各项行为数据吗? 想轻松查看用户行为图表吗? 想高效进行 App 运营管理吗? 想,来我带你玩转 App 数据统计.这里我使用专业.轻便的 JAnalytics. 本文内容分为两部分:代码示 ...
- leetcode-806-Number of Lines To Write String
题目描述: We are to write the letters of a given string S, from left to right into lines. Each line has ...
- tomcat 搭建以及发布配置
身为开发人员, 一直干着开发的事情, 只干开发的事情, 却缺少了对于环境部署方面的必备技能的培养, 所以在公司安排的手头任务解决完的情况下, 自己抽空了解并且实践了一下tomcat的配置.写下通过网络 ...
- [原创] Shell 参数传递 与 默认值
目录 简介 基本传参 $* 与 $@ 区别 默认参数(变量默认值) if 繁琐方式 - 变量为null = 变量为null时, 同时改变变量值 :- 变量为null 或 空字符串 := 变量为null ...
- CSS: Multiple Attribute Selector [name="value"][name2="value2"]
this.document.querySelectorAll('div[id*="dayselector"][class*="x-autocontainer-innerC ...