目录结构:

contents structure [+]
    1. 为什么需要异步IO操作
    2. C#的异步函数
      1. async和await的使用
      2. async和Task的区别
    3. 异步函数的状态机
      1. 异步函数如何转化为状态机
      2. 如何扩展异步函数
    4. FCL中的异步IO操作
      1. FileStream类
    5. 异步实现服务器
    6. 如何取消异步IO操作

在这篇文章中,笔者将会讨论如何执行异步的IO操作。上面一篇文章,笔者介绍了如何执行异步计算操作。在读完本文后,将异步应用到IO操作中,能够提高读取的效率。

1.为什么需要异步IO操作

关于异步操作,想必读者已知道异步IO操作,笔者在这里展示FileStream类读取本地文件的过程。首先展示FileStream类同步读取IO的流程图。

上面的执行流程中,在第4步Windows将IRP数据包传送给恰当的设备驱动的IRP队列(每个设备驱动程序都维护着自己的IRP队列,其中包含了机器上运行的所有进程发出的I/O请求)。在IRP数据包到达时,设备驱动程序将IRP信息传给物理硬件设备上的安装电路板,然后由硬件驱动设备执行请求的I/O操作,也就是第5个步骤。

当硬件驱动设备执行I/O操作期间,发出了I/O请求的线程将无事可做,所以Windows将线程变成睡眠线程,防止它浪费CPU的时间(步骤6)。这当然好,虽然线程不浪费时间,但其仍然浪费空间(内存),这当然就不好了。

当硬件设备执行完I/0操作。然后Windows会唤醒你的线程,把它调度给一个CPU,使其从内核模式返回至用户模式,然后返回至托管代码(步骤7、8、9)。

上面的步骤看起来很不错,但是依旧存在两个问题:1.请求的数量越来越多,创建的线程就越来越多,那么被阻塞的线程就会越来越多,这样会更浪费内存。2.用执行结果来响应请求,如果请求的数量非常多,那么解锁的阻塞线程也就很多,而且机器上的线程数都会远远大于CPU数,所以在阻塞线程被集中解锁期间CPU很有可能会频繁地发生上下文切换,损害性能。

下面展示Windows如何异步读取I/O流,仍然使用FileStream来构建对象,但是需要传递FileOptions.Asynchronous标志,告诉Windows希望文件的读/写以异步的方式进行。

在使用FileOptions.Asynchronous创建FileStream对象后,就应该使用ReadAsync(...)来读取文件,而不是Read(...)。在ReadAsync内部分配一个Task<Int32>对象来代表用于完成的读取操作的代码。然后ReadAsync调用Win32ReadFile函数(步骤1),ReadFile分配IRP数据包(步骤2),然后将其传递给Windows内核(步骤3)。Windows内核把IRP数据包添加到IRP队列中(步骤4)。此时线程不会再阻塞,而是可以直接运行返回至你的代码。所以线程能够立即从ReadAsync调用中返回(步骤5、6、7)。

在调用ReadAsync后返回一个Task<Int32>对象,可以在该对象上调用ContinueWith来登记任务完成时执行的回调方法,然后在回调方法中处理数据。当硬件设备处理好IRP后(步骤a)。硬件设备会把IRP放到CLR的线程池中队列中(步骤b)。将来某个时候,一个线程池会提取完成的IRP并执行任务的代码,最终要么设置异常(如果发生异常),要么返回结果(步骤c)。在知道这些之后,就知道使用异步I/O可以尽量的减少同步I/O访问存在的那些问题。

2.C#的异步函数

之前的一篇文章中,我们讨论了《计算限制的异步操作》,其中绝大部分代码都是使用Task来完成的。C#还为我们提供了另一种异步糖语法—异步函数,使用异步函数时可以以顺序的步骤写异步的代码,感觉像是在进行同步操作。Task和异步函数的功能类似,但他们之前还是有本质的差别。

2.1 async和await的使用

async和await是C#异步函数编程的核心,async和await是从.NET Framwork 4.5提供的新关键字,被async标记的方法表明该方法应该以异步的方式运行;await操作符用于标记异步函数执行完成后状态机恢复的位置(注意,这里不是等待),同时指示包含该await操作符的方法以异步的方式运行。如果async中不包含await,那么async会以同步的方式运行。
下面展示异步访问网络的步骤:

        static void Main(string[] args)
{
Task<int> task= AccessTheWebAsync();
task.ContinueWith((t) => {
Console.WriteLine(t.Result);
});
Console.ReadLine();
}
static async Task<int> AccessTheWebAsync() {
//需要引入System.Net.Http程序集
HttpClient httpClient = new HttpClient(); Task<string> getStringTask = httpClient.GetStringAsync("https://www.baidu.com/"); // await操作符挂起AccessTheWebAsync方法
// AccessTheWebAsync不能够继续执行,直到getStringTask任务完成。
// AccessTheWebAsync可以从这里直接异步返回给AccessTheWebAsync的调用者。
// 当getStringTask任务完成后,状态机可以直接从这里恢复,并且await操作符会返回任务的Result值。
String urlContents = await getStringTask; //返回长度
return urlContents.Length;
}

使用async和await有以下几点需要注意:
1.方法名应该以Async结尾(比如:AccessTheWebAsync)。
2.方法应该包含有async修饰符。
3.方法的返回类型应该是Task<TResult>或Task或void或其他类型(从C#7.0,.NET Core开始,其他类型的返回值应该提供GetAwaiter方法)。
4.方法中至少应该包含一个await表达式。

2.2 Async和Task的区别

Async和Task是非常相似的,但是都可以用于异步执行。但是他们之间也是有本质区别的,相信读者看完文章开篇的“为什么需要进行异步I/O操作”的过程已经有所了解了。接下来,笔者想再延伸一下,之所以要使用异步函数来进行异步I/O操作,而不推荐使用Task重新创建一个线程池线程来访问异步I/O操作,就是因为阻塞。

当异步函数的线程遇到阻塞时,并有线程被真正阻塞在哪里,当阻塞被完成后,再从线程池中唤醒一个线程用来执行之后的任务。当线程池遇到阻塞时,那么那个线程是被真正阻塞了的。

例如:

        static async void Test1()
{
Console.WriteLine("thread id before await:" + Thread.CurrentThread.ManagedThreadId); HttpClient hc = new HttpClient();
HttpResponseMessage hrm = await hc.GetAsync("http://www.baidu.com"); Console.WriteLine(hrm.StatusCode); Console.WriteLine("thread id after await:" + Thread.CurrentThread.ManagedThreadId);
}

通过这个异步函数,你一般都会看到await前后是被不同的线程执行的。

        static void Test2(){
Task task = new Task(() => {
Console.WriteLine("thread id:" + Thread.CurrentThread.ManagedThreadId);
//做一些阻塞动作
}); task.ContinueWith((tk) => {
Console.WriteLine("thread id:" + Thread.CurrentThread.ManagedThreadId);
}); task.Start();
}

通过这个,你会看到两个Thread id是相同的。

最后总结一下,

async和Task非常相似,都可以用于执行异步操作。但是异步函数(async)遇到阻塞后,其线程会被回收,用于执行线程池中的其他任务,当阻塞完成后,其会从线程池中唤醒另一个线程(这个线程和之前的线程也有可能是同一个线程,几率很小),用于执行await后面的动作。Task创建的额外线程遇到阻塞时,其创建的线程是会被阻塞的,直到阻塞完成后,线程才能继续执行。

3.异步函数的状态机

3.1 异步函数如何转化为状态机

通常情况下,观察编译器给我们编译好的代码,可以帮助我们更好的理解我们的代码。像async和await操作符,编译器其实是把这些操作符转化成了一种状态机的机制。将含有async和await的代码编译为IL代码,再将IL代码反编译为C#代码,就可以得到状态机。
比如:

    class Type { }

    class Program
{ private static async Task<Type> Method1()
{
/*执行一些异步操作,最后返回一个Type类型的数据*/
HttpClient httpClient = new HttpClient();
String result= await httpClient.GetStringAsync("http://www.baidu.com"); return new Type();
} private static async Task<String> MyMethodAsync() { Type result1 = await Method1(); return result1.ToString();
} static void Main(string[] args)
{
}
}

编译为IL代码后,再利用ILSPY把IL代码反编译为C#代码,在返编译IL代码的时候,需要注意,不能勾选“decompile async methods(async/await)”

然后就可以看到async和await转化成的状态机

通过查看反编译后的C#代码,C#中的异步函数的运行过程,可以用下图进行简单的概括:

但任务未完成时,isCompleted返回false,所以会在onCompleted登记任务完成时会调用的action动作,action动作执行完成后,会再一次调用MoveNext,然后isCompleted就返回true,此时就可以通过GetResult获得结果。

3.2 如何扩展异步函数

在扩展性方面,能用Task对象包装一个即将完成的操作,就可以使用await操作符来等待该操作。

下面是一个TaskLogger类,可用它显示未完成的异步操作。

    static class TaskLogger {
public enum TaskLogLevel { None,Pending}
public static TaskLogLevel LogLevel { get; set; } public sealed class TaskLogEntry {
public Task Task { get; internal set; }
public String Tag { get; internal set; }
public DateTime LogTime { get; internal set; }
public String CallerMemberName { get; internal set; }
public String CallerFilePath { get; internal set; }
public Int32 CallerLineNumber { get; internal set; }
public override string ToString()
{
return String.Format("LogTime={0},Tag={1},Member={2},File={3}({4})",
LogTime,Tag??"(none)",CallerMemberName,CallerFilePath,CallerLineNumber);
}
} private static readonly ConcurrentDictionary<Task, TaskLogEntry> s_log = new ConcurrentDictionary<Task, TaskLogEntry>(); public static IEnumerable<TaskLogEntry> GetLogEntries() { return s_log.Values;} public static Task<TResult> Log<TResult>(this Task<TResult> task, String tag = null,
[CallerMemberName] String callerMemberName=null,
[CallerFilePath] String callerFilePath=null,
[CallerLineNumber] Int32 callerLineNumber=-) { return (Task<TResult>)Log(task, tag, callerMemberName, callerFilePath, callerLineNumber);
} public static Task Log(this Task task, String tag = null,
[CallerMemberName] String callerMemberName = null,
[CallerFilePath] String callerFilePath = null,
[CallerLineNumber] Int32 callerLineNumber = -) {
if (LogLevel == TaskLogLevel.None) {
return task;
}
var logEntry = new TaskLogEntry {
Task=task,
LogTime=DateTime.Now,
Tag=tag,
CallerMemberName=callerMemberName,
CallerFilePath=callerFilePath,
CallerLineNumber=callerLineNumber
}; s_log[task] = logEntry; //附加一个异步任务,当一个任务执行完成后,应该将其从清单中移除
task.ContinueWith(t => {
TaskLogEntry entry;
s_log.TryRemove(t,out entry);
},TaskContinuationOptions.ExecuteSynchronously); return task;
}
}

Callation类,用于取消正在执行的异步操作

    static class Cancellation
{
public struct Void { } public static async Task WithCancellation(this Task originalTask, CancellationToken ct)
{
//创建在Cancellation被取消时完成的一个Task
var cancelTask = new TaskCompletionSource<Void>(); using (ct.Register(t => ((TaskCompletionSource<Void>)t).TrySetResult(new Void()), cancelTask)) { //创建在原始Task或CancellationToken Task完成时都完成的一个Task
Task any = await Task.WhenAny(originalTask,cancelTask.Task); //任务Task因为CancellationToken而完成,就抛出OperationCanceledException
if (any == cancelTask.Task)
ct.ThrowIfCancellationRequested();
}; //等待原始任务;若任务失败,它将抛出一个异常
await originalTask;
}
}

最后,展示如何使用

    class Program
{
static void Main(string[] args)
{
Go();
Console.ReadLine();
}
public static async Task Go() {
#if DEBUG
//使用TaskLogger会影响内存和性能,所以只在调试生成中启用它
TaskLogger.LogLevel=TaskLogger.TaskLogLevel.Pending;
#endif
//初始化3个任务;为了测试TaskLogger,我们显示控制持续时间
var tasks = new List<Task>{
Task.Delay().Log("2s op"),
Task.Delay().Log("5s op"),
Task<String>.Delay().Log("8s op"),
}; try
{
//等待全部任务,但在3秒后取消;只有一个任务能够按时完成
await Task.WhenAll(tasks).WithCancellation(new CancellationTokenSource().Token);
}
catch (OperationCanceledException) {
//查询logger哪些任务尚未完成,按照从等待时间从最长到最短的顺序排序
foreach (var op in TaskLogger.GetLogEntries().OrderBy(tle => tle.LogTime)) {
Console.WriteLine(op);
}
}
}
}

我的得到如下的输出结果:

LogTime=2018/11/7 1:30:41,Tag=8s op,Member=Go,File=e:\MyLearn\ConsoleApplication1\Program.cs(28)
LogTime=2018/11/7 1:30:41,Tag=5s op,Member=Go,File=e:\MyLearn\ConsoleApplication1\Program.cs(27)

除了增强使用Task的灵活性,异步函数对另一个扩展性有力的地方在于编译器可以在await的任何操作数上调用GetAwaiter。所以操作数不一定是Task对象。可以是任何任意类型,只要提供一个调用GetAwaiter的方法就可以了。
例如:

    public sealed class EventAwaiter<TEventArgs> : INotifyCompletion {
private ConcurrentQueue<TEventArgs> m_events = new ConcurrentQueue<TEventArgs>(); private Action m_continuation; //状态机调用GetAwaiter获得Awaiter,这里返回自己
public EventAwaiter<TEventArgs> GetAwaiter() { return this; } //告诉状态机是否发生了任何事件
public Boolean IsCompleted { get { return m_events.Count > ; } } //状态机告诉我们以后要调用什么方法,continuation中包含有恢复状态机的操作
public void OnCompleted(Action continuation) {
Volatile.Write(ref m_continuation,continuation);
} //状态机查询结果,这是awaiter操作符的结果
public TEventArgs GetResult() {
TEventArgs e;
m_events.TryDequeue(out e);
return e;
} public void EventRaised(Object sender, TEventArgs eventArgs) {
m_events.Enqueue(eventArgs); //如果有一个等待运行的延续任务,该线程会运行它
Action continuation = Interlocked.Exchange(ref m_continuation, null);
if (continuation != null) {
continuation();//恢复状态机
}
}
}

在EventAwaiter类在事件发生的时候从await操作符返回。在本例中,一旦AppDomain中的任何线程抛出异常,状态机就会继续。

        private static async void ShowException() {
var eventAwaiter = new EventAwaiter<FirstChanceExceptionEventArgs>();
AppDomain.CurrentDomain.FirstChanceException += eventAwaiter.EventRaised; while (true) {
Console.WriteLine((await eventAwaiter).Exception.GetType());
}
}

笔者自定义的EventAwaiter<TEventArgs>提供了GetAwaiter()、isCompleted()、onCompleted(Action continuation)、GetResult()几个重要的方法,其实这几个方法恰好对应了第3.1中“异步函数如何转化为状态机”中状态机需要操作的各个方法,在3.1中笔者给出一张状态机执行的流程图,这里就不再贴那张图片了。

笔者接下来结合这个案例,说一说本例的流程:

a.当执行到await eventAwaiter时,会去调用eventAwaiter的GetAwaiter()方法,然后得到Awaiter对象。

b.查询Awaiter对象和IsCompleted()方法,判断当前Awaiter是否发生了事件。

c.若Awaiter还没有发生事件,就调用OnCompleted(Action)方法,并且传递一个Action委托给OnCompleted()方法,其中的Action委托里就包含了恢复状态机的逻辑。

d.此时还没有线程执行恢复状态机的代码,await eventWaiter 的线程将会被阻塞。

e.当结合本例的程序逻辑,当出现异常时EventRaised会被调用,然后在EventRaised中会恢复状态机,唤醒await eventWaiter阻塞的线程。

f.状态机然后会再次调用IsCompleted方法判断是否有事件,这时m_events 已经有一个事件了,所以IsCompleted会返回true。

g.状态机接着调用GetResult,并且将结果值赋值给await关键字的表达式。

最后演示这一切是如何工作的:

       static void Main(string[] args)
{
ShowException(); for (int i = ; i < ; i++) {
try
{
switch (i) {
case : throw new InvalidCastException();
case : throw new InvalidOperationException();
case : throw new ArgumentException();
}
}
catch (Exception) {
}
}
Console.ReadLine();
}

4.FCL中的异步IO操作

FCL中的异步函数非常容易辨认,因为命名规范要求异步函数必须加上Async的后缀。在FCL中,支持I/O操作的许多类型都提供了XxxAsync方法
例如:
a.System.IO.Stream的所有派生类都提供了ReadAsync,WriteAsync,FlushAsync和CopyToAsync方法
b.System.IO.TextReader的所有派生类都提供了ReadAsync,ReadLineAsync,ReadToEndAsync和ReadBlockAsync方法。System.IO.TextWriter的派生类提供了WriteAsync,WriteLineAsync和FlushAsync.
c.System.Net.Http.HttpClient 类提供了GetAsync,GetStreamAsync,GetByteArrayAsync,PostAsync,PutAsync,DeleteAsync和其他许多方法。
d.System.Net.WebRequest的所派生类(包括FileWebRequest,FtpWebRequest和HttpWebRequest)都提供了GetRequestStreamAsync和GetResponseAsync方法。
e.System.Data.SqlClient.SqlCommand类提供了ExecuteDbDataReaderAsync,ExecuteNonQueryAsync,ExecuteReaderAsync,ExecuteScalarAsync和ExecuteXmlReaderAsync方法。
f.生成Web服务代理工具(比如SvcUtil.exe)也生成了XxxAsync方法。

这里笔者以System.Net.Http.HttpClient来举例:

        static async void Go() {
HttpClient httpClient = new HttpClient();
Stream stm = await httpClient.GetStreamAsync("http://www.baidu.com"); StreamReader sr = new StreamReader(stm);
String line= "";
while ((line = await sr.ReadLineAsync()) != null) {
Console.WriteLine(line);
}
}

FCL中有许多编程都使用了BeginXxx/EndXxx方法模型和IAsyncResult接口,还有基于事件的编程模型,它也提供了XxxAsync方法(不返回Task对象),能在异步操作完成时调用事件处理程序。这两种编程模型都已经过时,使用Task的新模型才是你的首要选择。

在FCL中,有一些类缺少XxxAsync方法,只提供了BeginXxx和EndXxx方法。可以通过TaskFactory将其转化为基于Task的模型。
BeginExecuteXXX 和EndExecuteXXX 使用TaskFactory来转化的步骤,例如:
返回值= Task.Factory.FromAsync(BeginEexcuteXXX,EndExecuteXXX,...);
返回值是EndExecute的返回值。

例如:NamedPipeServerStream类定义了BeginWaitForConnection和EndWaitForConnection,但是没有定义WaitForConnectionAsync方法,可以按照如下代码来完成转化。

        static async void StartServer() {
while (true) { //循环不停的接受来自客户端的链接
var pipe = new NamedPipeServerStream(c_pipeName,PipeDirection.InOut,-,PipeTransmissionMode.Message,PipeOptions.Asynchronous|PipeOptions.WriteThrough); //异步的接受来自客户端的连接
//用TaskFactory的FromAsync将旧的异步编程模型转化为新的Task模型
//当没有客户端连接时,线程将会挂起,并且允许方法已异步的方式返回调用者(本例中未有返回)
//当有客户端连接后,立即唤醒状态机,线程继续执行。
await Task.Factory.FromAsync(pipe.BeginWaitForConnection,pipe.EndWaitForConnection,null); //为客户端提供服务
//startServiceConnectionAsync 也是异步方法,所以能够立即返回
startServiceConnectionAsync(pipe);
}
}

FCL没有提供任何的辅助方法将旧的、基于事件的编程模型转化为新的、基于Task的编程模型。所有只能使用硬编码的方式。例如下面演示了使用TaskCompletionSource包装使用了“基于事件的编程模型”的WebClient,以便在异步函数中等待它。

        static async Task<String> AwaitWebClient(Uri uri) {
//System.Net.WebClient
var wc = new System.Net.WebClient(); //创建TaskCompletionSource及其基础Task对象
var tcs = new TaskCompletionSource<String>(); //字符串下载完成后,WebClient对象引发DownloadStringCompleted事件
wc.DownloadStringCompleted += (s, e) => {
if (e.Cancelled) tcs.SetCanceled();
else if (e.Error != null) tcs.SetException(e.Error);
else tcs.SetResult(e.Result);
}; //启动异步操作
wc.DownloadStringAsync(uri); //现在可以等待TaskCompletion
String result = await tcs.Task; return result;
}

4.1 FileStream类

创建FileStream对象时,可通过FileOptions.AsyncChronous标志指定以同步方式还是异步方式进行通信。如果不指定该标志,Windows将以同步方式执行所有文件操作。当然,仍然可以调用FileStream的ReadAsync方法,对于你的应用程序,表面上是异步执行,但FileStream类在内部用另一个线程模拟异步行为。这个额外的线程纯属是浪费。

如果创建FileStream对象时指定FileOptions.AsyncChronous标志。然后,可以调用FileStream的Read方法执行一个同步操作。在内部,FileStream类会开始一个异步操作,然后立即调用线程进入睡眠状态,直到操作完成才唤醒,从而模拟同步行为,这样依然效率低下。

总之,使用FileStream时应该想好是以同步方式还是以异步方式执行I/O操作,并指定FileOptions.Asynchronous标志来指明自己的选择。如果指定了该标志,就总是调用ReadAsync。如果没有使用这个标志,就总是调用Read。这样能够获得最佳性能。如果想先对FileStream执行一些同步操作,再执行一些异步操作,那么更高效的做法是使用FileOptions.Asynchronous标志来构造它。另外也可针对同一个文件,创建两个FileStream对象,一个FileStream进行同步操作,另一个FileStream执行异步操作。

FileStream的辅助方法(Create,Open和OpenWrite)创建并返回FileStream对象,这些方法都没有指定FileOptions.Asynchronous标志,所以为了实现响应灵敏的、可伸缩性的应用程序,应避免使用这些方法。

5.异步实现服务器

FCL内建了对伸缩性很好的一些异步服务器的支持。下面列举中MSDN文档中值的参考的地方。
1.要构建异步ASP.NET Web窗体,在.aspx文件中添加Async="true"的网页指令,并参考System.Web.UI.Page的RegisterAsyncTask方法。
2.要构建异步ASP.NET MVC控制器,使你的控制器类从System.Web.Mvc.AsyncController派生,让操作方法返回一个Task<ActionResult>即可。
3.要构建异步ASP.NET 处理程序,使你的类从System.Web.HttpTaskAsyncHandler派生,重写其ProcessRequestAsync方法。
4.要构建异步WCF服务,将服务作为异步函数来实现,让它返回Task或Task<TResult>。

这里笔者讲解一下如何构建异步的ASP.NET MVC控制器,如果是.NET 4.5(支持await和async关键词)以上以及4.5的版本,那么构建异步ASP.NET非常方便,例如:

public class TestController extends AsyncController{
public async Task<ActionResult> Get() {
Task<TResult> task = ...;
return await task;
}
}

上面的模型方法不会阻塞任何线程。在.NET 4.5 以下的话,并不支持async和await,我们仍然可以通过AsyncController来实现不阻塞任何线程的异步服务器响应:

    public class TestController : AsyncController
{
//开始异步会调用该方法
public void getAsync() {
//声明异步操作
AsyncManager.OutstandingOperations.Increment();
Task.Factory.StartNew(() => {//开始异步操作
//在这里可以进行耗时的操作,并不会有线程等待
//将结果赋值给AsyncManager.Parameters
AsyncManager.Parameters["response"] = "abc";
//异步结束
AsyncManager.OutstandingOperations.Decrement();
});
}
//异步结束时,会调用该方法
//参数必需和AsyncManager.Parameters赋值key一样
public ActionResult getCompleted(String response)
{
return Content(response.ToString(), "text/json");
}
}

上面虽然声明了两个控制器方法getAsync和getCompleted,但实际上只有get,访问也只能通过get。

在使用AsyncManager进行数据传递的时候,AsyncManager是和控制器相关联的,也就是说如果有Simple控制器和Test控制器,那么Simple控制器中的AsyncManager是不能干扰Test控制器中的AsyncManager的数的。

更详细的内容,可以看这篇文章:https://msdn.microsoft.com/cs-cz/library/ee728598(v=vs.100).aspx

6.如何取消异步IO操作

Windows一般没有提供取消未完成I/O操作的途径,这是许多开发人员都想要的功能,实现起来却很困难。毕竟,如果向服务器请求了1000个字节,然后决定不再需要这些字节,那么其实没有办法告诉服务器忘掉你的请求。在这种情况下,只能让字节照常返回,再将他们丢弃。此外,这里还发生竞态条件-取消请求的请求可能正在服务器发送响应的时候到来,要在代码中处理这种潜在的竞态条件,决定是丢弃还是使用数据。

建议实现一个WithCancellation扩展方法Task<TResult>(需要重载版本来扩展Task)上面的案例中,我们已经使用过Task的扩展版本了,下面是Task<TResult>版本:

static class CancelleationClass {
private struct Void { }//没有泛型的TaskCompletionSource类 public static async Task<TResult> WithCancellation<TResult>(this Task<TResult> originalTask, CancellationToken ct)
{
//创建在CancellationToken被取消时完成的一个Task
var cancelTask = new TaskCompletionSource<Void>(); //一旦CancellationToken被取消,就完成Task
CancellationTokenRegistration cancellationTokenRegistration = ct.Register(t =>
{
((TaskCompletionSource<Void>)t).TrySetResult(new Void());
}, cancelTask); //创建在原始task或cancel task完成时都完成的Task
Task any = await Task.WhenAny(originalTask, cancelTask.Task); //只要是cancel task先完成,就抛出OperationCanceledException
if (any == cancelTask.Task)
{
ct.ThrowIfCancellationRequested();
} //释放资源
cancellationTokenRegistration.Dispose(); //返回原始任务
return originalTask.Result;
}
}

按照如下的代码来使用它:

public static async Task<Int32> go() {
var cts = new CancellationTokenSource();
var ct = cts.Token; try
{
Int32 max = ;
Task<Int32> task = new Task<Int32>(() => {
Int32 result = ;
for (int i = ; i < max; i++) {
result += i;
Thread.Sleep();
}
return result;
});
task.Start(); //在指定的时间后取消操作
Task.Delay().ContinueWith((obj) =>
{
cts.Cancel();
}); Int32 res=await task.WithCancellation<Int32>(ct); return res;
}
catch (OperationCanceledException e) {
Console.WriteLine(e.Message);
}
return -;
}

【C#】C#线程_I/O限制的异步操作的更多相关文章

  1. C# 线程知识--使用ThreadPool执行异步操作

    C# 线程知识--使用ThreadPool执行异步操作 在应用程序中有许多复杂的任务,对于这些任务可能需要使用一个或多个工作线程或I/O线程来协作处理,比如:定时任务.数据库数据操作.web服务.文件 ...

  2. C# 线程知识--使用Task执行异步操作

    在C#4.0之前需要执行一个复杂的异步操作时,只能使用CLR线程池技术来执行一个任务.线程池执行异步任务时,不知道任务何时完成,以及任务的在任务完成后不能获取到返回值.但是在C#4.0中引人了一个的任 ...

  3. 【C#】C#线程_计算限制的异步操作

    目录结构: contents structure [+] 线程池简介 执行上下文(Execution Context) CancelTokenSource的使用 ThreadPool Task和Tas ...

  4. C#多线程之线程池篇2

    在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...

  5. 【C#进阶系列】27 I/O限制的异步操作

    上一章讲到了用线程池,任务,并行类的函数,PLINQ等各种方式进行基于线程池的计算限制异步操作. 而本章讲的是如何异步执行I/O限制操作,允许将任务交给硬件设备来处理,期间完全不占用线程和CPU资源. ...

  6. WebServer中异步操作的一些总结

    1.异步操作本身不会改善IO的性能 2.当任务多为IO操作时普通的工作线程将会减少,使CPU对工作线程的维护降低,从而提高CPU对其它任务的利用率 3.如果专用的IO线程,需要执行的专用任务较多时,专 ...

  7. C#高级知识点概要(2) - 线程和并发

    原文地址:http://www.cnblogs.com/Leo_wl/p/4192935.html 我也想过跳过C#高级知识点概要直接讲MVC,但经过前思后想,还是觉得有必要讲的.我希望通过自己的经验 ...

  8. 【温故而知新-万花筒】C# 异步编程 逆变 协变 委托 事件 事件参数 迭代 线程、多线程、线程池、后台线程

    额基本脱离了2.0 3.5的时代了.在.net 4.0+ 时代.一切都是辣么简单! 参考文档: http://www.cnblogs.com/linzheng/archive/2012/04/11/2 ...

  9. 《CLR Via C#》读书笔记:26.线程基础

    一.线程开销 操作系统创建线程是有代价的,其主要开销在下面列举出来了. 内存开销 线程内核对象 拥有线程描述属性与线程上下文,线程上下文占用的内存空间为 x86 架构 占用 700 字节.x64 架构 ...

随机推荐

  1. Visual Studio 2019及其注册码

    Visual Studio 2019 更快地进行代码编写.更智能地执行操作.使用同类最佳IDE 创建未来.     下载Visual Studio         使用从初始设计到最终部署的完整工具集 ...

  2. POJ 3126 Prime Path【BFS】

    <题目链接> 题目大意: 给你两个四位数,它们均为素数,以第一个四位数作为起点,每次能够变换该四位数的任意一位,变换后的四位数也必须是素数,问你是否能够通过变换使得第一个四位数变成第二个四 ...

  3. CodeForces - 893C Rumor【并查集】

    <题目链接> 题目大意: 有n个人,其中有m对朋友,现在你有一个秘密你想告诉所有人,第i个人愿意出价a[i]买你的秘密,获得秘密的人会免费告诉它的所有朋友(他朋友的朋友也会免费知道),现在 ...

  4. POJ 1328 安装雷达 (贪心)

    <题目链接> 题目大意: 以x轴为分界,y>0部分为海,y<0部分为陆地,给出一些岛屿坐标(在海中),再给出雷达可达到范围,雷达只可以安在陆地上,问最少多少雷达可以覆盖所以岛屿 ...

  5. 流网络分析系统-SNAS

    流网络分析系统-SNAS SNAS,Streaming Network Analytics System (project SNAS) ,是一个收集.跟踪.存取 千万条实时路由对象的系统. 官网:ht ...

  6. Windows下MySQL数据目录修改

    最近在弄一个项目,需要修改windows下面的mysql的数据目录(mysql默认是存储在C盘的,这个不太好,未来一定会满了的). 我的 mysql 是安装版,版本是 5.7. 1.首先,需要知道my ...

  7. 路由网关---zuul

    Zuul:Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架.Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门. 在微服务盛行的时代,客户端与系统之 ...

  8. UVA 815 Flooded!

    题意:来自:https://blog.csdn.net/lecholin/article/details/70186673 思路: ①数组存每个网格的高度,然后排序,做题时想象为上面的柱状图. ②注意 ...

  9. System.ServiceModel.AddressAccessDeniedException

    发生了 System.ServiceModel.AddressAccessDeniedException   HResult=0x80131501   Message=HTTP 无法注册 URL ht ...

  10. Lua的 table.sort排序

    在用table.sort 排序的时候注意,如果使用多个条件排序,应在一个排序函数里按照条件优先级进行比较排序. 例如 local t = { {time = , i = }, {time = , i ...