• 【读书笔记】C#高级编程 第十三章 异步编程


    (一)异步编程的重要性

    使用异步编程,方法调用是在后台运行(通常在线程或任务的帮助下),并不会阻塞调用线程。有3中不同的异步编程模式:异步模式、基于事件的异步模式和新增加的基于任务的异步模式(TAP,可利用async和await关键字来实现)。

     

     

    (二)异步模式

    1、C#1的APM 异步编程模型(Asynchronous Programming Model)。

    2、C#2的EAP 基于事件的异步模式(Event-based Asynchronous Pattern)。

    3、TAP 基于任务的异步模式(Task-based Asynchronous Pattern)。

    参考:http://www.cnblogs.com/zhaopei/p/async_one.html

     

     

    (三)异步编程基础

    async和await关键字只是编译器功能。编译器会用Task类创建代码。

     

    1、创建任务

     1 /// <summary>
     2 /// 同步方法
     3 /// </summary>
     4 /// <returns></returns>
     5 static string SayHi(string name)
     6 {
     7     Thread.Sleep(3000);
     8     return "你好!"+ name;
     9 }
    10 
    11 /// <summary>
    12 /// 基于任务的异步模式
    13 /// </summary>
    14 /// <returns></returns>
    15 static Task<string> SayHiAsync(string name)
    16 {
    17     return Task.Run<string>(()=> {
    18         return SayHi(name);
    19     });
    20 }

    泛型版本的Task.Run<string>创建一个返回字符串的任务。

     

    2、调用异步方法

    使用await关键字需要有用async修饰符声明的方法。在await的方法没有完成前,该方法内的其他代码不会继续执行,但是调用await所在方法的线程不会被阻塞。

    private async static void CallerWithAsync()
    {
        string result = await SayHiAsync("张三");
        Console.WriteLine(result);
    }

    async修饰符只能用于返回Task和void方法。

     

    3、延续任务

    Task类的ContinueWith方法定义了任务完成后就调用的代码。指派给ContinueWith方法的委托接受将已完成的任务作为参数传入,使用Result属性可以访问任务返回的结果。

    private static void CallerWithContinuationTask()
    {
        Task<string> t = SayHiAsync("李四");
        t.ContinueWith(_t => Console.WriteLine(_t.Result));
    }

     

    4、同步上下文

    WPF应用程序设置了DispatcherSynchronizationContext属性,WindowsForm应用程序设置了WindowsFormsSynchronizationContext属性。如果调用异步方法的线程分配给了同步上下文,await完成之后将继续执行。如果不使用相同的上下文,必须调用Task类的ConfigureAwait(ContinueOnCapturedContext:false)。

     

    5、使用多个异步方法

    (1)按顺序调用异步方法

    private async static void MultipleCallerWithAsync()
    {
        string result1 = await SayHiAsync("张三");
        string result2 = await SayHiAsync("李四");
        Console.WriteLine("完成了两次打招呼!{0} 和 {1}", result1, result2);
    }

     

    (2)使用组合器

    一个组合器可以接受多个同一类型的参数,并返回同一类型的值。Task组合器接受多个Task对象作为参数,并返回一个Task。

    private async static void MultipleCallerWithAsyncWithCombinators1()
    {
        Task<string> task1 =  SayHiAsync("张三");
        Task<string> task2 =  SayHiAsync("李四");
        await Task.WhenAll(task1,task2);
        Console.WriteLine("完成了两次打招呼!{0} 和 {1}", result1, result2);
    }

    Task类定义了WhenAll(全部任务完成才返回)和WhenAny(任意任务完成即返回)两个组合器。

    当任务返回类型相同时,可以用数组接受返回结果。

    private async static void MultipleCallerWithAsyncWithCombinators2()
    {
        Task<string> task1 =  SayHiAsync("张三");
        Task<string> task2 =  SayHiAsync("李四");
        string [] results=await Task.WhenAll(task1,task2);
        Console.WriteLine("完成了两次打招呼!{0} 和 {1}", results[0], results[1]);
    }

     

    6、转换异步模式

    当某些类没有提供基于任务的异步模式时(仅有BeginXX,EndXX),可以使用TaskFactory类定义的FromAsync方法转换为基于任务的异步模式的方法。

    private static async void ConvertingAsyncPattern()
    {
        Func<string, string> method = SayHi;
        string result = await Task<string>.Factory.FromAsync<string>((name, callback,state) => {
            return method.BeginInvoke(name, callback, state);
        },ar=> {
            return method.EndInvoke(ar);
        },"王麻子",null);
    Console.WriteLine(result);
    }

     

    (四)错误处理

    1、异步方法的异常处理

    异步方法异常的一个较好的处理方式,就是使用await关键字,将其放在try/catch语句中。

     1 static async Task ThrowAfter(int ms, string message)
     2 {
     3     await Task.Delay(ms);
     4     throw new Exception(message);
     5 }
     6 
     7 private static async void HandleOneError()
     8 {
     9     try
    10     {
    11         await ThrowAfter(2000, "first");
    12     }
    13     catch (Exception ex)
    14     {
    15         Console.WriteLine("handled {0}", ex.Message);
    16     }
    17 }

     

    2、多个异步方法的异常处理

    在顺序调用两个及以上会抛出异常的方法时,不可再使用以上方法,因为当第一个异步方法抛出异常时try块里的余下方法不会再被调用。

    如果需要将剩余的方法继续执行完,再对异常进行处理,可以使用Task.WhenAll方法,这样不管任务是否抛出异常,都会等到所有任务执行完。但是,也只能看见传递给WhenAll方法的第一个异常。

    private static async void HandleOneError()
    {
        try
        {
            Task task1 = ThrowAfter(1000, "first");
            Task task2 = ThrowAfter(2000, "second");
            await Task.WhenAll(task1, task2);
        }
        catch (Exception ex)
        {
            Console.WriteLine("handled {0}", ex.Message);
        }
    }

    如果需要将剩余的方法执行完,且获取所有抛出异常,可以在try块外声明任务变量,使其可以在catch块内被访问。这样可以使用IsFaulted属性检查任务的状态,当为true时,可以使用Task类的Exception.InnerException访问异常信息。

    private static async void HandleOneError()
    {
        Task task1 = null;
        Task task2 = null;
        try
        {
            task1 = ThrowAfter(1000, "first");
            task2 = ThrowAfter(2000, "second");
            await Task.WhenAll(task1, task2);
        }
        catch (Exception ex)
        {
            if (task1 != null && task1.IsFaulted)
            {
                Console.WriteLine(task1.Exception.InnerException);
            }
            if (task2 != null && task2.IsFaulted)
            {
                Console.WriteLine(task2.Exception.InnerException);
            }
        }
    }

     

    3、使用AggregateException信息

    为了得到所有的异常信息,还可以将Task.WhenAll返回的结果写入一个Task变量中,然后访问Task类的Exception属性(AggregateException类型)。AggregateException定义了InnerExceptions属性,它包含了所有的异常信息。

    private static async void HandleOneError()
    {
        Task task = null;
        try
        {
            Task task1 = ThrowAfter(1000, "first");
            Task task2 = ThrowAfter(2000, "second");
            await (task = Task.WhenAll(task1, task2));
        }
        catch (Exception)
        {
            foreach (var exception in task.Exception.InnerExceptions)
            {
                Console.WriteLine(exception);
            }
        }
    }

     

    (五)取消

    1、取消任务

    private CancellationTokenSource cts;
    private void OnCancel()
    {
        if (cts != null)
        {
            cts.Cancel();
            //cts.CancelAfter(1000);//等待1000ms后取消
        }
    }

     

    2、使用框架特性取消任务

    private async void OnTaskBasedAsyncPattern()
    {
        List<string> urlList = new List<string>();
        urlList.Add("http://www.baidu.com");
        cts = new CancellationTokenSource();
        try
        {
            foreach (var url in urlList)
            {
                Random rd = new Random();
                int i = rd.Next(1, 100); //1到100之间的数,
                if (i%2==0)
                {
                    OnCancel();//当随机数为偶数时取消任务
                }
                var client = new HttpClient();
                var response = await client.GetAsync(url, cts.Token);//GetAsync方法会检查是否应该取消操作
                var result =await response.Content.ReadAsStringAsync();
                Console.WriteLine(result);
            }
        }
        catch (OperationCanceledException ex)//当任务取消时会抛出该异常
        {
            Console.WriteLine(ex.Message);
        }
    }

     

    3、取消自定义任务

    Task类的Run方法提供了传递CancellationToken参数的重载版本。使用IsCancellationRequest属性检查令牌,用ThrowIfCancellationRequested方法触发异常。

    public async void CustomerTask()
    {
        cts = new CancellationTokenSource();
        var list = new List<string>();
        list.Add("1");
        list.Add("2");
        list.Add("3");
        var deal_list = new List<int>();
        try
        {
            await Task.Run(() => {
                foreach (var item in list)
                {
                    Random rd = new Random();
                    int i = rd.Next(1, 100); //1到100之间的数,
                    if (i % 2 == 0)
                    {
                        OnCancel();//当随机数为偶数时取消任务
                    }
                    if (cts.Token.IsCancellationRequested)
                    {
                        Console.WriteLine("处理任务异常,回滚");
                        deal_list.Clear();
                        cts.Token.ThrowIfCancellationRequested();
                    }
                    deal_list.Add(Convert.ToInt32(item));
                    Console.WriteLine(item);
                }
            }, cts.Token);
        }
        catch (OperationCanceledException ex)
        {
    
            Console.WriteLine(ex.Message);
        }
       
    }
  • 相关阅读:
    汉诺塔3(递推)
    逃生(拓扑排序)
    洛谷P2048 [NOI2010]超级钢琴 (优先队列+RMQ)
    有条件的最短路
    hdu 5950 Recursive sequence(矩阵快速幂)
    线性基模板
    P2023 [AHOI2009]维护序列 (线段树区间修改查询)
    hdu 6534 Chika and Friendly Pairs(莫队+树状数组)
    2019 计蒜之道 复赛
    poj 2449 k短路(A*+spfa)
  • 原文地址:https://www.cnblogs.com/dlxh/p/6697397.html
Copyright © 2020-2023  润新知