使用专用线程执行受限制的异步操作
需要创建线程池以外线程的情况:
1.如果希望线程以一个特殊的优先级(所有线程池中的线程都是以普通优先级运行,而且我们不应该修改线程池中线程的优先级)运行,就需要创建一个专用的线程。
2.同样,所有线程池中的线程都是后台线程,若希望让一个线程成为前台线程,也可以考虑创建并使用自己的线程,从而阻止应用程序“死亡”,直至线程完成任务。
3.如果受限制的任务运行时间特别长,还会使用专用线程。这样我们就不必让线程池的逻辑去费力地判断是否还需要创建额外的线程。
4.如果希望启动一个线程,然后通过调用Thread的Abort方法过早地中断该线程的话,也可以使用一个专用线程。
异步操作的另一种方法:Join(),此方法导致调用线程停止执行任何代码,直至其等待的线自己销毁自己或者被终止。
定期执行受计算限制的异步操作
FCL中提供的三个定时器:
System.Threading.Timer:希望在另一个线程上定时执行后台任务时,这个定时器是最好的定时器。
System.Windows.Forms.Timer:随着定时器的触发,Windows将一个定时器消息(WM_TIMER),插入到线程的消息队列中,调用线程必须执行一个消息泵(message pump),从而提取消息,并将它们分派到期望的回调方法中。注意,所有这些工作都是由一个线程完成的——设置定时器的线程是保证执行回调方法的线程。这同样意味着我们的定时器不能被多个线程同时执行。
System.Timers.Timer:该类实际上是对System.Threading中的Timer类的包装,不建议使用。
异步编程模型简介
异步编程模式(APM)的具体示例:
所有派生自System.IO.Stream并与硬件设备(包括FileStream和NetworkStream)进行通信的类都提供了BeginRead和BeginWrite方法。注意,派生自Stream的类不与硬件设备进行通信的类(如BufferedStream,MemoryStream以及CrytoStream)进行通信的类也提供了BeginRead和BeginWrite方法适合APM。但是这些方法中的代码只执行受计算限制的操作,而不能执行受I/O限制的操作,因此需要一个线程来执行这些操作。
另外,所有的委托类型都定义了一个BeginInvoke方法来使用APM。
APM的一个主要特征就是它提供了三个聚集的技巧,而线程池中并没有提供内置的方法来供我们查找异步操作是何时完成的。
由于I/0的速度较慢且运行情况不可预知,适合采用异步的方式来实现,网络也存在同样的情况。若使用FileStream来实现异步读写,首先指定FileOptions.Asynchronous标记,通过BeginRead/BeginWrite实现异步读写。
APM的三个聚集技巧
APM支持以下三种聚集技巧:等待直至完成(wait-until-done)、轮询(polling)、方法回调(method callback)。
wait-until-done:为了启动一个异步操作,我们可以调用一些Begin方法,所有这些方法都会将请求操作排队,然后返回一个IAsyncResult对象标识挂起的操作。为了获得操作的结果,我们可以以IAsyncResult对象为参数调用相应的End方法。
polling:利用IAsyncResult对象中的IsCompleted,以及WaitHandle AsyncWaitHandle委托来循环判断是否已经完成异步操作。不建议使用,因为这实际上是浪费了一个线程的时间来查看异步请求是否已经完成。
method callback:在所有的APM聚集技巧中,构建高性能和可扩展应用程序架构时,方法回调聚集技巧最好用。原因在于该技巧永远不会将一个线程置于等待状态(与wait-until-done聚集技巧不同),而且该技巧不会定期地检查异步操作是否已经完成而浪费CPU时间(与polling聚集技巧也不同)。
其工作原理是:首先将异步请求排队等候,然后线程继续执行它希望执行的任何事情。接着,当请求完成时,windows将工作项加入CLR的线程池队列中。最后,线程池中的线程将工作项从队列中取出,并调用我们编写的一些方法,通过这种方式我么可以知道异步操作已经完成。然后,在回调方法内部,我们首先调用End方法获取异步操作的结果,然后就可以自由地继续处理结果。
异步线程的执行上下文
/// <summary>
/// 为了节省效率,有事会阻止异步线程拷贝当前线程的上下文,但这是一个相当危险的做法
/// 除非你确定访问的数据是无关紧要的,因为这样做所有的安全权限都无法拷贝,导致
/// 异步线程可以执行任何操作
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
//显示方法工作正常
AttemptAccess("Defaut context");
//修改线程的执行上下文,从而不允许任何文件访问
new FileIOPermission(PermissionState.Unrestricted).Deny();
//读取属性失败,因为线程的执行上下文被修改了
AttemptAccess("No file permission");
//
ECFlowing();
//阻止上下文移动
ECFlowingSuppressed();
//捕获线程的当前上下文
ExecutionContext ec = ExecutionContext.Capture();
//修改线程执行的上下文,从而允许任何访问
SecurityPermission.RevertDeny();
//显示方法工作正常
AttemptAccess("Default context again");
//显示方法无法正常工作
ECCaptureAndRun(ec);
Console.ReadLine();
}
private static void ECFlowing()
{
//初始化一委托变量来引用希望个异步调用的方法
WaitCallback wc = AttemptAccess;
//注意这里用BeginInvoke作为EndInvoke的参数,
//这里使用线程池线程进行访问,当线程池线程返回线程池后,调用EndInvoke返回
wc.EndInvoke(wc.BeginInvoke("ECFlowing", null, null));
}
private static void ECFlowingSuppressed()
{
WaitCallback wc = AttemptAccess;
//临时告诉CLR不要将这个线程的执行上下文流到辅助线程中去
using (AsyncFlowControl afc = ExecutionContext.SuppressFlow())
{
wc.EndInvoke(wc.BeginInvoke("ECFlowingSuppressed", null, null));
}
}
private static void ECCaptureAndRun(ExecutionContext ec)
{
ExecutionContext.Run(ec,
AttemptAccess,
"ECCaptureAndRun with Run");
}
private static void AttemptAccess(object test)
{
bool success = false;
try
{
//尝试获得文件的属性
File.GetAttributes("111.txt");
//若获得了文件的属性,则方法调用成功
success = true;
}
catch
{
//因为线程的执行上下文的安全权限不足,所以方法调用失败
}
//显示所执行的访问尝试是成功还是失败
Console.WriteLine("{0}: {1}", test, success);
}
}