美文网首页简书面面观
C#沉淀-异步编程 二

C#沉淀-异步编程 二

作者: 东南有大树 | 来源:发表于2018-09-30 12:22 被阅读14次

    针对于await表达式的异常处理

    using System;
    using System.Net;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using System.Threading;
    
    namespace CodeForAsync
    {
    
        class Program
        {
            //定义一个异步方法
            static async Task BadAsync()
            {
                try
                {
                    //故意抛出一个异常
                    await Task.Run(() => { throw new Exception(); });
                }
                catch (Exception ex)
                {
                    //捕获异常
                    Console.WriteLine("捕获一个异步"+ex.ToString());
                }
            }
            
            static void Main(string[] args)
            {
                Task t = BadAsync();
                t.Wait();
    
                Console.WriteLine("查看异步方法的状态:"+t.Status);
                Console.WriteLine("查看是否有未经处理的异常:"+t.IsFaulted);
    
                Console.ReadKey();
            }
        }
    }
    

    输出:

    捕获一个异步System.Exception: 引发类型为“System.Exception”的异常。
       在 CodeForAsync.Program.<BadAsync>b__0() 位置 f:\我的文档\文档\C# 沉淀\CodeForAsync\CodeForAsync\Program.cs:行号 18
       在 System.Threading.Tasks.Task`1.InnerInvoke()
       在 System.Threading.Tasks.Task.Execute()
    --- 引发异常的上一位置中堆栈跟踪的末尾 ---
       在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       在 System.Runtime.CompilerServices.TaskAwaiter.GetResult()
       在 CodeForAsync.Program.<BadAsync>d__2.MoveNext() 位置 f:\我的文档\文档\C# 沉淀\CodeForAsync\CodeForAsync\Program.cs:行号 18
    查看异步方法的状态:RanToCompletion
    查看是否有未经处理的异常:False
    

    从输出结果来看,Task的状态为RanToCompletion,说明即使捕获到异常也不会令Task终止或取消;IsFaulted的状态为False,说明没有未处理的异常

    在调用方法中同步的等待任务

    Task提供了一个实例方法Wait,可以在Task上调用该方法,同步的等待任务的完成

    示例:

    using System;
    using System.Net;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace CodeForAsync
    {
        static class MyDownloadString
        {
            public static void DoRun()
            {
                Task<int> t1 = CountCharactersAsync(1, "http://baidu.com");
    
                //等待任务结束
                t1.Wait();
                Console.WriteLine("任务已经结束,值:" + t1.Result);
            }
    
            //下载网站资源
            private static async Task<int> CountCharactersAsync(int id, string uristring)
            {
                string result = await (new WebClient()).DownloadStringTaskAsync(new Uri(uristring));
                return result.Length;
            }
        }
        
        class Program
        {
            static void Main(string[] args)
            {
                MyDownloadString.DoRun();
    
                Console.ReadKey();
            }
        }
    }
    

    这里的Wait用在了单一的Task对象上,也可以用于一组Task上,但是需要用到Task上的两个静态方法:

    示例:WaitAllWaitAny

    using System;
    using System.Net;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace CodeForAsync
    {
        class MyDownloadString
        {
            Stopwatch sw = new Stopwatch();
    
            public void DoRun()
            {
                //下载百度资源
                Task<int> t1 = CountCharactersAsync(1, "http://baidu.com");
                //下载搜狗资源
                Task<int> t2 = CountCharactersAsync(2, "https://pinyin.sogou.com/");
    
                ///WaitAll会等待所包含的所有的Task都完成后才会往下执行
                ///WaitAny会等待所包含的所有的Taks中只要有一个完成即会向下执行
                Task<int>[] tasks = new Task<int>[] { t1, t2 };
                //Task.WaitAll(tasks);
                Task.WaitAny(tasks);
    
                ///下面的写法也是允许的
                ///这两个静态方法接收的是Taks类型的数组
                ///下面实现了隐式的转换
                //Task.WaitAll(t1, t2);
                //Task.WaitAny(t1, t2);
    
                Console.WriteLine("任务一是否完成:" + (t1.IsCompleted ? "完成" : "未完成").ToString());
                Console.WriteLine("任务二是否完成:" + (t2.IsCompleted ? "完成" : "未完成").ToString());
            }
    
            //下载网站资源
            private async Task<int> CountCharactersAsync(int id, string uristring)
            {
                WebClient wc = new WebClient();
                string result = await wc.DownloadStringTaskAsync(new Uri(uristring));
    
                return result.Length;
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                MyDownloadString ds = new MyDownloadString();
                ds.DoRun();
    
                Console.ReadKey();
            }
        }
    }
    

    输出:

    任务一是否完成:完成
    任务二是否完成:未完成
    

    WaitAll与WaitAny有许多重载,这里简单介绍两个:

    //等待所有任务完成,或CancellationToken发出了取消信号
    public static void WaitAll(Task[] tasks, CancellationToken cancellationToken);
    
    //等待所有任务完成,如果在等待时间内仍未完成,则继续往下执行
    public static bool WaitAll(Task[] tasks, int millisecondsTimeout);
    public static bool WaitAll(Task[] tasks, TimeSpan timeout);
    
    //等待所有任务完成,如果发生超时或者有取消动作,则继续往下执行不再等待
    public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);
    
    /////////////////////////////////////////
    
    //等待一个任务完成,或CancellationToken发出了取消信号
    public static void WaitAny(Task[] tasks, CancellationToken cancellationToken);
    
    //等待一个任务完成,如果在等待时间内仍未完成,则继续往下执行
    public static bool WaitAny(Task[] tasks, int millisecondsTimeout);
    public static bool WaitAny(Task[] tasks, TimeSpan timeout);
    
    //等待一个任务完成,如果发生超时或者有取消动作,则继续往下执行不再等待
    public static bool WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);
    
    

    在异步方法中异步的等待任务

    await会等待一个或所有的任务完成,

    可以通过Task.WhenAllTask.WhenAny方法来实现。这两个方法称为组合子

    示例:

    using System;
    using System.Net;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using System.Collections.Generic;
    
    namespace CodeForAsync
    {
        class MyDownloadString
        {
            public void DoRun()
            {
                //下载百度资源
                Task<int> t = CountCharactersAsync("http://baidu.com", "https://pinyin.sogou.com");
                Console.WriteLine("任务t是否已经完成:{0}", t.IsCompleted ? "完成" : "未完成");
                Console.WriteLine("输出值:{0}", t.Result);
            }
    
            //下载网站资源
            private async Task<int> CountCharactersAsync(string uristring1, string uristring2)
            {
                WebClient wc1 = new WebClient();
                WebClient wc2 = new WebClient();
    
                Task<string> t1 = wc1.DownloadStringTaskAsync(new Uri(uristring1));
                Task<string> t2 = wc2.DownloadStringTaskAsync(new Uri(uristring2));
    
                List<Task<string>> tasks = new List<Task<string>>();
                tasks.Add(t1);
                tasks.Add(t2);
                
                //await Task.WhenAll(tasks);
                await Task.WhenAny(tasks);
    
                Console.WriteLine("任务一是否完成:{0}", t1.IsCompleted ? "完成" : "未完成");
                Console.WriteLine("任务二是否完成:{0}", t2.IsCompleted ? "完成" : "未完成");
    
                return t1.IsCompleted ? t1.Result.Length : t2.Result.Length;
    
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                MyDownloadString ds = new MyDownloadString();
                ds.DoRun();
    
                Console.ReadKey();
            }
        }
    }
    

    输出:

    任务t是否已经完成:未完成
    任务一是否完成:完成
    任务二是否完成:未完成
    输出值:81
    

    await 会等待一个Task任务的完成,如果await 调用Task.WhenAll,则会异步的等待所有方法都被调用完;而await调用Task.WhenAny时,则会异步的等待其中一个任务完成;同样,当代码遇到await时,会返回到调用方法上去

    上例中使用了Task.WhenAny(tasks);,所以,当任务一完成而任务二未完成时,代码便向下执行了,不再等待,如果换成Task.WhenAll(tasks);,则代码会等待两个任务完成后才会向下执行

    WaitAll/WaitAny/WhenAll/WhenAny之间的区别

    最好理解的是WaitAllWaitAny,它们都是发生在调用层的等待,阻塞当前线程,等待所有的异步方法有所返回以后才会继续当前的线程

    WhenAllWhenAny也是等待,不过不是在调用层的线程中等待,而是在异步方法里等待,所以这里说它是“异步的等待”,它阻塞的是异步方法的内部过程,而调用方法因为遇到await会反加到调用层,所以调用层的线程不会被阻塞

    也就是说它们的效果是一样的,但所在的包装层级不同

    Task.Delay方法

    Task.Delay方法会使任务暂停一段时间,我们知道Thread.Sleep也可以将程序暂停一段时间,不同的是,Task.Delay就在异步方法内部使用,不会导致调用层线程阻塞,而Thread.Sleep即使是在异步方法内部他用,也可能使用调用层被阻塞

    示例:

    using System;
    using System.Net;
    using System.Diagnostics;
    using System.Threading.Tasks;
    using System.Collections.Generic;
    using System.Threading;
    
    namespace CodeForAsync
    {
        class MyClass
        {
            public async void Hello()
            {
                Console.WriteLine("节点1");
    
                Thread.Sleep(1000);
                // await Task.Delay(1000);
    
                await Task.Run(() =>
                {
                    Console.WriteLine("节点2");
                });
            }
    
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                MyClass mc = new MyClass();
                mc.Hello();
    
                Thread.Sleep(100);
                Console.WriteLine("节点3");
    
                Console.ReadKey();
            }
        }
    }
    

    输出:

    节点1
    节点2
    节点3
    

    Thread.Sleep(1000);会将当前线程与调用层的线程都阻塞,如果使用await Task.Delay(1000);的话,输出如下:

    节点1
    节点3
    节点2
    

    Task.Delay(1000)际上只是创建了一个消耗1000毫秒的任务罢了,因此他需要额外的消耗资源

    相关文章

      网友评论

        本文标题:C#沉淀-异步编程 二

        本文链接:https://www.haomeiwen.com/subject/aqhwoftx.html