C#异步迭代 IAsyncEnumerable 应用

最近用WPF做金税盘开发中有这样一个需求,批量开票每次开票都需要连接一次金税盘。

比如我有发票 a, b ,c ,d e 这五张发票,每次开具发票都需要调用金税盘底层,才能正常开票。

首先,尝试写第一个方法

 private  void Button_Click(object sender, RoutedEventArgs e)
        {
            var dateStart = DateTime.Now;  //记录用时的起始时间
            DebugText = string.Empty;
            List<string> fpList = new List<string>() { "a", "b", "c", "d" };

            foreach (var item in MockIO)
            {
                var dateEnd = DateTime.Now;
                var timeSpan = dateEnd - dateStart;//记录开票用时
                DebugText += item + " " + timeSpan.TotalSeconds + "\r\n";

            }
        }

     /// <summary>
        /// 批量开票方法
        /// </summary>
        /// <param name="ls"></param>
        /// <returns></returns>
        public static IEnumerable<string> MockIO(List<string> ls)
        {

            foreach (var item in ls)
            {
                Task.Delay(1000).Wait();
                yield return item;
                Debug.WriteLine(Thread.GetCurrentProcessorId());
            }
        }

来看效果

 

 

很明显,发生了UI阻塞情况。因为我们并未对代码做任何异步处理。接下来,我们开始尝试修改。

首先,我们尝试按照常规异步方法修改 MockIO 函数,增加 async 关键词,返回结果增加 Task, 内部对IO操作添加 await

修改完毕后,编译并没有通过,VS对该方法报异常

 

通过提示信息,我们可以发现,返回值 Task<IEnumerable<string>> 并不是可以迭代的,因为我们采用了 yield 来返回值,所以我们需要一个可以迭代的返回值。

比如改成这样(非迭代的方式)

调用:

 

 

但是,这样一次就返回一组 Task ,没有用到方便的 yield;

此时,就可以用到 IAsyncEnumerable 来设计了,IAsyncEnumerable是C# 8.0引入的新特性,在异步迭代中,非常方便。如上述代码,可以直接修改为

  public static async IAsyncEnumerable<string> MockIOAsync(List<string> ls)
        {
            foreach (var item in ls)
            {
                Task<Task<string>> task = Task<Task<string>>.Factory.StartNew(async () =>
               {
                   await Task.Delay(1000);
                   return item;

               });

                yield return await task.Result;
            }
        }

调用(因为采用yield方式,每次返回一个对象都是异步的,所以需要在foreach前加 await)

我们再运行调试,看一下效果

 

 

我们可以看到,不仅UI没有被阻塞,同时,传回的值也是一个接一个的传过来的,符合我们的预期。

 

扩展:虽然上述步骤我们完成的UI的非阻塞的实现,但是我们整个开票用时并没有节省。

接下来,我将继续修改 MockIOAsync 方法,将实现迭代器内部的多线程操作。

修改后的代码如下

    public static async Task<IEnumerable<string>> MockIOPerformanceAsync(List<string> ls)
        {
            List<string> lss = new List<string>();
            List<Task> tasks = new List<Task>();
            foreach (var item in ls)
            {

                Task task = new Task(() =>
              {
                  Task.Delay(1000).Wait();
                  Debug.WriteLine(Thread.GetCurrentProcessorId());
                  lss.Add(item);
              });
                tasks.Add(task);
                task.Start();

            }
            foreach (var item in tasks)
            {
                await item;
            }
            return lss;
        }

效果展示:

 

 

 嗯,速度很快,但是排序乱了,因为此方法在遍历中新建了线程,list 添加并不保证按照迭代器的顺序添加。有得有失。

 

源码地址:https://github.com/LuGuangguang/IAsyncEnumerableDemo

 

 

 

 

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 百度翻译

 

翻译 朗读 复制 正在查询,请稍候…… 重试 朗读 复制 复制 朗读 复制 via 百度翻译

posted on 2021-06-25 17:21  鲁广广  阅读(1628)  评论(0编辑  收藏  举报

导航