当前位置 博文首页 > 鲁广广:C#异步迭代 IAsyncEnumerable 应用

    鲁广广:C#异步迭代 IAsyncEnumerable 应用

    作者:鲁广广 时间:2021-06-25 18:27

    最近用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;
                }
            }

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

     

     

    我们可以看到,不仅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 添加并不保证按照迭代器的顺序添加。有得有失。

     

     

     

     

     

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

     

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

    bk
    下一篇:没有了