什麼是 Yield
Yield 就是 .Net 中用來實作 iterator(迭代器) 設計模式的語法糖,雖然很早就知道這東西,但一直都不太知道怎麼用,剛好最近同事在翻寫寄送大量信件的程式有用到就跟他請教了一下。
以前的做法
將收件人從 DB 撈出來後 ,依據每封信的 Template 去置換內容,再批次送去給寄信服務執行
問題
如果這批收件人很多,把整批的信件都置換完 Template 再送出,會導致系統吃掉大量的記憶。當然最簡單的解決方法就是用迴圈組完一筆就送出一次,但其實用 Yield Return 可以更優雅的解決這個問題
範例
同事提供了簡單的範例來理解這件事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| static void Main(string[] args) { foreach (var mail in GetData()) { Console.WriteLine($"- Received: {mail.Title}"); } }
static List<Mail> GetData() { var mailList = new List<Mail>(); int buffer_size = 1024 * 1024 * 4; Random _rnd = new Random();
for (int index = 0; index < 1024; index++) { var _buffer = new byte[buffer_size]; _rnd.NextBytes(_buffer);
mailList.Add(new Mail() { Title = $"buffer[{index}], {buffer_size / 1024 / 1024} MB", Buffer = _buffer, }); Task.Delay(50).Wait(); } return mailList; }
public class Mail { public string Title { get; set; } public byte[] Buffer { get; set; } }
|
GetData()
這是模擬處理信件的地方,每封信件假設 4MB (到底是寄啥…),整批處理完後回傳 mailList
如果把這段 Code 放到 LINQPad 執行,會發現記憶體一直往上飆,因為會全部都做完放到 List 再回傳並寄信
Yield Return 版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| static void Main(string[] args) { foreach (var mail in GetData()) { Console.WriteLine($"- Received: {mail.Title}"); } }
static IEnumerable<Mail> GetData() { int buffer_size = 1024 * 1024 * 4; Random _rnd = new Random();
for (int index = 0; index < 1024; index++) { var _buffer = new byte[buffer_size]; _rnd.NextBytes(_buffer);
yield return new Mail() { Title = $"buffer[{index}], {buffer_size / 1024 / 1024} MB", Buffer = _buffer, }; Task.Delay(50).Wait(); }
yield break; }
public class Mail { public string Title { get; set; } public byte[] Buffer { get; set; } }
|
觀察記憶體會發現非常的平穩,因為逐筆處理完就被消滅掉了
小結
至於如果不用語法糖 Yield 該如何實作迭代器,可參考安德魯的部落格裡面介紹非常詳細,希望這個方法以後可以更融會貫通的套在專案中使用