【C#】C#线程_I/O限制的异步操做

目录结构:编程

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,使其从内核模式返回至用户模式,而后返回至托管代码(步骤七、八、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调用中返回(步骤五、六、7)。

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

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操做,就是由于阻塞。async

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

例如:异步编程

        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=-1) {

                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 = -1) {
               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(2000).Log("2s op"),
                Task.Delay(5000).Log("5s op"),
                Task<String>.Delay(8000).Log("8s op"),
            };

            try
            {
                //等待所有任务,但在3秒后取消;只有一个任务可以按时完成
                await Task.WhenAll(tasks).WithCancellation(new CancellationTokenSource(3000).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 > 0; } }

        //状态机告诉咱们之后要调用什么方法,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 = 0; i < 3; i++) {
                try
                {
                    switch (i) {
                        case 0: throw new InvalidCastException();
                        case 1: throw new InvalidOperationException();
                        case 2: 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,-1,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 = 10;
        Task<Int32> task = new Task<Int32>(() => {
            Int32 result = 0;
            for (int i = 0; i < max; i++) {
                result += i;
                Thread.Sleep(1000);
            }
            return result;
        });
        task.Start();

        //在指定的时间后取消操做
        Task.Delay(500).ContinueWith((obj) =>
        {
            cts.Cancel();
        });

        Int32 res=await task.WithCancellation<Int32>(ct);

        return res;
    }
    catch (OperationCanceledException e) {
        Console.WriteLine(e.Message);
    }
    return -1;
}
相关文章
相关标签/搜索