{"componentChunkName":"component---src-templates-blog-post-js","path":"/blog/iasyncenumerable-to-jest-dobre","result":{"data":{"markdownRemark":{"html":"<p>C# 8 wprowadził pewną nowinkę - <code class=\"language-text\">IAsyncEnumerable</code>. Co prawda na horyzoncie jest już C# 9, ale jakoś ta nowinka z poprzedniej wersji jakoś nie trafiła pod strzechy - a może być w pewnych sytuacjach bardzo przydatna.</p>\n<p>W największym skrócie <code class=\"language-text\">IAsyncEnumerable</code> umożliwia korzystanie z <code class=\"language-text\">yield return</code> w metodach oznaczonych słowem kluczowym <code class=\"language-text\">async</code> - wcześniej nie było to możliwe. Ktoś powie: \"zaraz, zaraz - przecież dotychczas mogłem napisać sygnaturę metody w stylu <code class=\"language-text\">async Task&lt;IEnumerable&lt;SomeType>></code>, co nie?\". To prawda - ale taka sygnatura oznaczała, że asynchronicznie zwracamy enumerację, jednak już samo enumerowanie asynchroniczne nie jest. Tymczasem <code class=\"language-text\">IAsyncEnumerable</code> to coś więcej - to asynchroniczne enumerowanie, to znaczy: każdy \"krok\" enumeracji jest asynchroniczny. Co to oznacza? Na poniższym przykładzie można zobaczyć różnicę. Mamy dwa podejścia do wybrania pierwszego elementu z \"asynchronicznej enumeracji\" - z <code class=\"language-text\">Task&lt;IEnumerable&lt;int>></code> oraz <code class=\"language-text\">IAsyncEnumerable&lt;int></code>. Jaki jest wynik na konsoli?</p>\n<div class=\"gatsby-highlight\" data-language=\"csharp\"><pre style=\"counter-reset: linenumber NaN\" class=\"language-csharp line-numbers\"><code class=\"language-csharp\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">TestRun</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">static</span> <span class=\"token keyword\">async</span> <span class=\"token return-type class-name\">Task</span> <span class=\"token function\">Run</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> test <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">Test</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        Console<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Task&lt;IEnumerable&lt;int>>:\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">(</span><span class=\"token keyword\">await</span> test<span class=\"token punctuation\">.</span><span class=\"token function\">DoWithIEnumerable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">FirstOrDefault</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n        Console<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"\\nIAsyncEnumerable&lt;int>:\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">await</span> test<span class=\"token punctuation\">.</span><span class=\"token function\">DoWithIAsyncEnumerable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">FirstOrDefaultAsync</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Test</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">private</span> <span class=\"token class-name\"><span class=\"token keyword\">int</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span></span> numbers <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">{</span> <span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">4</span><span class=\"token punctuation\">,</span> <span class=\"token number\">8</span><span class=\"token punctuation\">,</span> <span class=\"token number\">16</span><span class=\"token punctuation\">,</span> <span class=\"token number\">32</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">public</span> <span class=\"token keyword\">async</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span>IEnumerable<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">int</span><span class=\"token punctuation\">></span><span class=\"token punctuation\">></span></span> <span class=\"token function\">DoWithIEnumerable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token constructor-invocation class-name\">List<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">int</span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">foreach</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> number <span class=\"token keyword\">in</span> numbers<span class=\"token punctuation\">)</span>\n            <span class=\"token punctuation\">{</span>\n                <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> item <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">ReturnWithDelay</span><span class=\"token punctuation\">(</span>number<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                result<span class=\"token punctuation\">.</span><span class=\"token function\">Add</span><span class=\"token punctuation\">(</span>item<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n            <span class=\"token keyword\">return</span> result<span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n        <span class=\"token keyword\">public</span> <span class=\"token keyword\">async</span> <span class=\"token return-type class-name\">IAsyncEnumerable<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">int</span><span class=\"token punctuation\">></span></span> <span class=\"token function\">DoWithIAsyncEnumerable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">foreach</span> <span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">var</span></span> number <span class=\"token keyword\">in</span> numbers<span class=\"token punctuation\">)</span>\n            <span class=\"token punctuation\">{</span>\n                <span class=\"token class-name\"><span class=\"token keyword\">var</span></span> item <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">ReturnWithDelay</span><span class=\"token punctuation\">(</span>number<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                <span class=\"token keyword\">yield</span> <span class=\"token keyword\">return</span> item<span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span>\n        <span class=\"token keyword\">private</span> <span class=\"token return-type class-name\">Task<span class=\"token punctuation\">&lt;</span><span class=\"token keyword\">int</span><span class=\"token punctuation\">></span></span> <span class=\"token function\">ReturnWithDelay</span><span class=\"token punctuation\">(</span><span class=\"token class-name\"><span class=\"token keyword\">int</span></span> x<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">return</span> Task<span class=\"token punctuation\">.</span><span class=\"token function\">Run</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span>\n            <span class=\"token punctuation\">{</span>\n                Console<span class=\"token punctuation\">.</span><span class=\"token function\">WriteLine</span><span class=\"token punctuation\">(</span><span class=\"token interpolation-string\"><span class=\"token string\">$\"Waiting 2 seconds before returning... </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token expression language-csharp\">x</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">\"</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                Task<span class=\"token punctuation\">.</span><span class=\"token function\">Delay</span><span class=\"token punctuation\">(</span><span class=\"token number\">2000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                <span class=\"token keyword\">return</span> x<span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code><span aria-hidden=\"true\" class=\"line-numbers-rows\" style=\"white-space: normal; width: auto; left: 0;\"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>\n<p>Wyjście:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre style=\"counter-reset: linenumber NaN\" class=\"language-text line-numbers\"><code class=\"language-text\">Task&lt;IEnumerable&lt;int>>:\nWaiting 2 seconds before returning... 2\nWaiting 2 seconds before returning... 4\nWaiting 2 seconds before returning... 8\nWaiting 2 seconds before returning... 16\nWaiting 2 seconds before returning... 32\n\nIAsyncEnumerable&lt;int>:\nWaiting 2 seconds before returning... 2</code><span aria-hidden=\"true\" class=\"line-numbers-rows\" style=\"white-space: normal; width: auto; left: 0;\"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></pre></div>\n<p>Widzicie różnicę? W przypadku <code class=\"language-text\">Task&lt;IEnumerable&lt;int>></code> musimy de facto najpierw asynchronicznie zbudować całą naszą kolekcję, aby ją zwrócić klientowi. Co z tego, że klient chce tylko pierwszy element, my i tak musimy zbudować całość. Tymczasem w <code class=\"language-text\">IAsyncEnumerable</code> enumerujemy tak, jak w \"klasycznym\" <code class=\"language-text\">IEnumerable</code>: jeśli <em>caller</em> chce tylko jeden element, to \"napracujemy\" się tylko nad wybraniem tego jednego elementu.</p>\n<p>Przykład jest dosyć sztuczny, nasuwa się zatem pytanie, kiedy takie coś możne nam się przydać. Otóż, dobrym przykładem (sam ostatnio używałem w takiej sytuacji <code class=\"language-text\">IAsyncEnumerable</code>) jest wyciąganie listy dokumentów z Cosmos DB: ze względu na charakterystykę komunikacji z Cosmosem, de facto takiej listy nie dostaniemy jednym zapytaniem do bazy. Cosmos niejako \"paginuje\" (\"porcjuje\") wyniki - czasem potrzeba kilku strzałów do Cosmosa, aby wybrać wyszystkie dokumenty z listy. A każdy taki strzał jest asynchroniczny. Widzimy więc od razu, że w przypadku, gdy <em>caller</em> potrzebuje tylko podzbioru wyników (w skrajnym przypadku - chce woła tylko <code class=\"language-text\">FirstOrDefault</code>), nie musimy robić wszystkich zapytań do Cosmosa, by wpierw zbudować całą listę wynikową.</p>\n<p>Zatem wszędzie tam, gdzie budowanie enumeracji jest asynchroniczne - możemy zyskać stosując <code class=\"language-text\">IAsyncEnumerable</code>. Dzięki temu unikamy \"ukrytego kosztu\": gdy metoda przedstawia się jako <code class=\"language-text\">Task&lt;IEnumerable&lt;T>></code>, to nie wiemy (bez zaglądania do jej implementacji), co jest tak naprawdę asynchroniczne w budowaniu takiej enumeracji: czy każdy krok wymaga asynchronicznej operacji, czy może tylko jakiś krok wstępny/przygotowawczy. Natomiast przy <code class=\"language-text\">IAsyncEnumerable</code> jest to przejrzyste dla konsumenta naszej metody.</p>\n<p>Na koniec - dwie sprawy, na które należy zwrócić uwagę:</p>\n<ul>\n<li>po pierwsze - <code class=\"language-text\">IAsyncEnumerable</code> jest \"awaitowalne\", czyli - jak w przykładzie powyżej - nie trzeba już go \"opakowywać\" w <code class=\"language-text\">Task&lt;...></code>,</li>\n<li>jeśli chcemy coś wyciągnąć z <code class=\"language-text\">IAsyncEnumerable</code> za pomocą LINQ-owych metod, to musimy doinstalować NuGeta <code class=\"language-text\">System.Linq.Async</code> i korzystać z asyncowych odpowiedników - np. <code class=\"language-text\">FirstOrDefaultAsync</code>.</li>\n</ul>","excerpt":"C# 8 wprowadził pewną nowinkę - . Co prawda na horyzoncie jest już C# 9, ale jakoś ta nowinka z poprzedniej wersji jakoś nie trafiła pod strzechy - a może być w…","frontmatter":{"date":"14 December, 2020","path":"/blog/iasyncenumerable-to-jest-dobre","title":"IAsyncEnumerable - to jest dobre!"},"fields":{"readingTime":{"text":"3 min read"}}}},"pageContext":{}},"staticQueryHashes":["3649515864","63159454"]}