C#沉淀-异步编程 二

C#沉淀-异步编程 二

针对于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毫秒的任务罢了,因此他需要额外的消耗资源