异步编程基础

1.异步编程基础

首先声明,我是一个很菜很菜的菜鸟程序员,最近在学习异步编程,在看《C#并发编程经典实例》这本书,打算在看的过程中,在博客中做个记录,顺便把比较好的地方摘抄出来和大家分享。包括自己写的一些小段测试程序,希望能在大家学习路上一些小小的帮助。

1.1暂停一段时间

问题

需要让程序等待一段时间,这在实现单元测试或者实现重试延迟非常有用。

解决方案

Task类有一个返回Task对象的静态函数Delay,这个Task对象会在指定的时间后完成。

单元测试示例

下面的例子用于单元测试,定义了一个异步完成的任务。在模拟一个异步操作时,至少需要测试“同步成功”,“异步成功”和”异步失败三种情况“下面的例子返回一个Task对象,用于异步成功测试。

        static async Task<T> DelayResult<T>(T result,TimeSpan timeSpan)
        {
            await Task.Delay(timeSpan);
            Console.WriteLine("async is complete");
            return result;
        }

测试代码

再来看一个示例代码:
定义一个异步方法,并同时用Task.Delay是实现一个异步等待。通过比较首先执行完成的Task返回对象,判断异步方法是否执行超时。

        static void Main(string[] args)
        {
            Task task = GetMetFask();
            Console.ReadKey();
        }
        private static async Task<string> GetMetFask()
        {
            var tk1r = Methond1();

            var tk2r = Task.Delay(10000);//等待操作

            string str=string.Empty;

            var completedtask =await Task.WhenAny(tk1r, tk2r);//判断哪个任务首先完成

            if (completedtask == tk1r)
            {
                str =("Methond1完成");
            }
            if (completedtask == tk2r)
            {
                str = ("Methond2完成");
            }
            Console.WriteLine(str);
            return str;
        }

        
        private static async Task Methond1()
        {
            await Task.Run(()=>{
                for (int i = 0; i < 100; i++)
                {
                    Thread.Sleep(10);
                }
            });
        }

Task.Delay适用于对异步代码进行单元测试或者实现重试逻辑,要实现超时功能的话,最好使用CancellationToken。

1.2返回完成的任务

问题
如何实现一个具有异步签名的同步方法。如果从异步接口或基类继承代码,但希望用同步方法来实现。对异步代码进行单元测试,以及用简单的生成方法存根(Stub)或者模拟对象(mock)来产生异步接口,这两种情况都可以用这个技术。

生成方法存根:生成方法存根 (Stub) 是一项 IntelliSense 自动代码生成功能,它提供了一种简便的方法,使 Visual Studio 在您编写方法调用时创建新的方法声明。Visual Studio 从调用推导声明。

解决方法
可以使用Task.FromResult方法创建并返回一个新的Task对象,这个Task对象已经是完成的,并且又正确的返回值。

示例代码

    public class MySynchronousImplemention : IMyAsyncInterface
    {
        //实现异步方法
        public Task<int> GetV()
        {
            return Task.FromResult<int>(13);//只能提供结构正确的同步Task对象
        }
    }
    /// <summary>
    /// 异步接口
    /// </summary>
    public interface IMyAsyncInterface
    {
        Task<int> GetV();//异步方法
    }

Task.FromResult只能提供结果正确的同步Task对象,如果返回的Task对象有一个其他的类型结果(例如以NotImplementedException结束的Task对象)就需要自行创建使用TaskCompletion Source的辅助方法。

        /// <summary>
        /// 创建TaskCompletionSource辅助方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        static Task<T> NotImplementedAsync<T>()
        {
            var tcs = new TaskCompletionSource<T>();
            tcs.TrySetException(new NotImplementedException());
            return tcs.Task;
        }

1.3 报告进度

【问题】
异步操作执行过程中,需要展示操作进度。

解决方案
使用IProgress< T>和Progress< T>类型。编写的async方法需要有IProgress< T>参数,其中T是需要报告的进度类型。

示例代码如下

static async Task MyMethondAsync(IProgress<double> progress=null)
        {
            double percentComplete = 0;
            //报告进度操作
            while (!done)
            {
                percentComplete++;

                if (percentComplete>99)
                {
                    done = true;
                }

                if (progress!=null)
                {
                    progress.Report(percentComplete);
                }
            }
        }

        static async Task CallMyMethondAsync()
        {
            var progress = new Progress<double>();
            progress.ProgressChanged += (sender, args) =>
              {
                  //进度改变后事件操作
                  Console.WriteLine("正在进行");
              };
            await MyMethondAsync(progress);
        }

1.3 等待一组任务完成

【问题】执行几个任务,等待他们全部完成

解决方案
框架提供的Task.WhenAll方法可以实现这个功能。这个方法输入为若干个任务,当所有的任务完成返回一个完整的Task对象

2.6 任务完成时的处理

【问题】正在await一批任务,希望每个任务完成时对它进行一些处理。另外,希望在任务已完成就立即进行处理,无需等待其他任务。

代码实例说明

   【注意:此代码并非解决问题的方法】
   
    class Program
    {
        static async Task<string> DelayAndReturnAsync(int val)
        {
            await Task.Delay(TimeSpan.FromSeconds(val));
            return val.ToString();
        }

        static async Task ProcessTaskAsync()
        {
            Task<string> taskA = DelayAndReturnAsync(3);
            Task<string> taskB = DelayAndReturnAsync(5);
            Task<string> taskC = DelayAndReturnAsync(1);
            var tasks = new Task<string>[] { taskA, taskB, taskC };//此集合要设置异步返回数据类型【或var tasks = new [] { taskA, taskB, taskC };】
            foreach (var item in tasks)
            {
                var result = await item;
                Console.WriteLine(result);
            }
        }

        static async Task Main(string[] args)
        {
            await ProcessTaskAsync();
        }
    }

虽然TaskC是首先完成的,但是这段代买仍按照列表的顺序对任务进行await,我们希望按任务完成的次序进行处理而,不用等待其他任务。

解决方案

引用更高级的async方法来await任务对结果进行并行处理。

    class Program
    {
        static async Task<int> DelayAndReturnAsync(int val)
        {
            await Task.Delay(TimeSpan.FromSeconds(val));
            return val;
        }

        static async Task AwaitAndProcessAsync(Task<int> task)
        {
            var result = await task;
            Console.WriteLine(result);
        }

        static async Task ProcessTaskAsync()
        {
            Task<int> taskA = DelayAndReturnAsync(3);
            Task<int> taskB = DelayAndReturnAsync(5);
            Task<int> taskC = DelayAndReturnAsync(1);
            var tasks = new [] { taskA, taskB, taskC };
            var processingTask = (from t in tasks select AwaitAndProcessAsync(t)).ToArray();
            await Task.WhenAll(processingTask);
        }

        static async Task Main(string[] args)
        {
            await ProcessTaskAsync();
        }
    }
【重构,提出处理过程】
        static async Task ProcessTaskAsync()
        {
            Task<int> taskA = DelayAndReturnAsync(3);
            Task<int> taskB = DelayAndReturnAsync(5);
            Task<int> taskC = DelayAndReturnAsync(1);
            var tasks = new [] { taskA, taskB, taskC };
            var processingTask = tasks.Select(async t =>
            {
                var result =await t;
                Console.WriteLine(result);
            }).ToArray();
            await Task.WhenAll(processingTask);
        }

2.7 处理async Task方法的异常

可以使用简单的try和catch来捕获异常,如下

        static async Task Main(string[] args)
        {
            await AsyncException();
        }

        static async Task ThrowExceptionAsync()
        {
            await Task.Delay(TimeSpan.FromSeconds(3));
            throw new InvalidOperationException("Error Message");
        }

        static async Task AsyncException()
        {
            try
            {
                await ThrowExceptionAsync();
            }
            catch (InvalidOperationException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
      注意:只有Task对象被await调用时,才会引发异常。

【本博客中理论内容和部分示例代码来自《C#并发编程经典实例》[美]Stephen Cleary(著) 相银初(译)】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值