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;
            }
        }

我們再運行調試,看一下效果

 

 

我們可以看到,不僅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 百度翻譯


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM