Async i Await rozbrajanie pułapek przez Łotrzyka z .NET-em na poziomie 9.0 Preview 4

Cezary Walenciuk

Async i Await
rozbrajanie pułapek przez Łotrzyka
z .NET-em
na poziomie 9.0 Preview 4

@walenciukC

Speaker
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW

Klasa Thread bez parametru

                    
                        static void Main(string[] args)
                        {
                            Thread thread = new Thread(new ThreadStart(Do));
                            thread.IsBackground = true;
                            thread.Start();
                            thread.Join();
                        }
                
                        public static void Do()
                        {}
                    
                

ThreadPool

                    
                        static void Main(string[] args)
                        {
                            ThreadPool.QueueUserWorkItem(Do);
                        }
                
                        public static void Do(object? state)
                        {}
                    
                

Możesz dodać "zadanie"(workitem) do puli.

IAsyncResult, czyli jak kiedyś robiło się asynchroniczne operacje

                    

                        static void Main(string[] args)
                        {              
                            Func<int, int> method =
                                (int a) => { return a; };
                
                            IAsyncResult handle =
                                method.BeginInvoke(100,
                                new AsyncCallback(WhenDone),
                                new object());
                        }
                
                        private static void WhenDone(IAsyncResult ar)
                        {
                            var target = (Func<int, int>)ar.AsyncState;
                            int result = target.EndInvoke(ar);
                
                            Console.WriteLine(result);
                        }
                    
                

Event Asynchronous Pattern

                    
                        static void Main(string[] args)
                        {
                            var worker = new BackgroundWorker();
                
                            worker.DoWork += WorkerDoSomething;
                
                            worker.ProgressChanged += Worker_ProgressChanged;
                            worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
                            worker.RunWorkerAsync(worker);
                        }
                
                        private static void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
                        {}
                        private static void WorkerDoSomething(object sender, DoWorkEventArgs e)
                        {}
                        private static void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
                        {}
                    
                

blog
CW
blog
CW
blog
CW

Przykład zablokowania wątku UI w WPF

                    



                        private void Button_Click(object sender, RoutedEventArgs e)
                        {
                            Do();
                        }
                        
                        private void Do()
                        {
                            txtBlock.Text += "Rozpoczynam liczenie";
                        
                            var result = HeavyCalculation(txt_X.Text, txt_Y.Text);
                        
                            txtBlock.Text += result;
                        }
                        
                        private string HeavyCalculation(string xT, string yT)
                        {
                            var x = int.Parse(xT);  
                            var y = int.Parse(yT);
                        
                            int addition = x + y; int subtraction = x - y;
                            int multiplication = x * y; double divison = x / y;
                        
                            Thread.Sleep(4000);
                        
                            return $"\n{addition}, {subtraction}, {multiplication}, {divison}\n";
                        }
                    
                

Rozwiązanie tego problemu przy pomocy Task API z .NET Framework 4

                    
                        private void Do()
                        {
                            txtBlock.Text += "Rozpoczynam liczenie";
                        
                            var taskRun = Task.Run(() =>HeavyCalculation(txt_X.Text, txt_Y.Text));
                        
                            taskRun.ContinueWith(ct =>
                            {
                                    txtBlock.Text += ct.Result;
                            });
                        
                            taskRun.ContinueWith(ct =>
                            {
                                if (ct.IsFaulted)
                                {
                                    txtBlock.Text += ct.Exception;
                                }
                            });
                        }
                    
                
blog
CW

Rozwiązanie tego problemu przy pomocy Task API z .NET Framework 4 PLUS synchronizacja wątku UI

                    


                        



                        private void Do()
                        {
                            txtBlock.Text += "Rozpoczynam liczenie";
                            var x = txt_X.Text; var y = txt_Y.Text;
                            var taskRun = Task.Run(() =>HeavyCalculation(x, y));
                        
                            taskRun.ContinueWith(ct =>
                            {
                                Dispatcher.Invoke(() =>
                                {
                                    txtBlock.Text += ct.Result;
                                });
                            });
                        
                            taskRun.ContinueWith(ct =>
                            {
                                if (ct.IsFaulted)
                                {
                                    Dispatcher.Invoke(() =>
                                    {
                                        txtBlock.Text += ct.Exception;
                                    });
                                }
                            });
                        }
                    
                
blog
CW

Task.Unwrap

                    
                        Task<Task<Task<int>>> t =
                            Task.Factory.StartNew
                            (() =>
                            {
                                return Task.Factory.StartNew(
                                () =>
                                Task.Run(() => { return 1; }));
            
                            });
            
                        var result = t.Result.Result.Result;
                    
                

Task.Unwrap

                    
                        Task<Task<Task<int>>> t =
                            Task.Factory.StartNew
                            (() =>
                            {
                                return Task.Factory.StartNew(
                                () =>
                                Task.Run(() => { return 1; }));
            
                            });
                                    
                        var unwrap = t.Unwrap();
                        var unwrap2 = unwrap.Unwrap();
                        var result = unwrap2.Result;
                    
                
blog
CW

WaitAll i WaitAny

                    
                        Task t1 = Task.Delay(2000);
                        Task t2 = Task.Delay(4000);
                        Task t3 = Task.Delay(6000);
            
                        Task.WaitAll(t1, t2, t3);
                        Task.WaitAny(t1, t2, t3);
                    
                

blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW

Async i Await z .NET Framework 4.5

                    






                        private async void Button_Click(object sender, RoutedEventArgs e)
                        {
                            await DoAsync();
                        }
                        
                        private async Task DoAsync()
                        {
                            txtBlock.Text += "Rozpoczynam liczenie";
                        
                            var result = await HeavyCalculationAsync(txt_X.Text, txt_Y.Text);
                        
                            txtBlock.Text += result;
                        }
                        
                        private async Task<string> HeavyCalculationAsync(string xT, string yT)
                        {
                            var x = int.Parse(xT);
                            var y = int.Parse(yT);
                        
                            int addition = x + y; int subtraction = x - y;
                            int multiplication = x * y; double divison = x / y;
                        
                            await Task.Delay(4000);
                        
                            return $"\n{addition}, {subtraction}, {multiplication}, {divison}\n";
                        }
                    
                
blog
CW
blog
CW

Async i Await obsługa wyjątku

                    
                        private async Task DoAsync()
                        {
                            txtBlock.Text += "Rozpoczynam liczenie";
                            string result;
                        
                            try
                            {
                                result  = await HeavyCalculationAsync(txt_X.Text, txt_Y.Text);
                            }
                            catch (Exception ex)
                            {
                                result = ex.ToString();
                            }
                        
                            txtBlock.Text += result;
                        }
                    
                
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW

Ile wątków ten kod stworzy?

                    
                        static async Task Main()
                        {
                            WriteThread("Begin Main");
                            var a = await M1Async();
                            WriteThread("Middle Main");
                            var b = await M1Async();
                            Console.WriteLine(a + b);
                            WriteThread("End Main");
                            Console.ReadKey();
                        }
                
                        public static async Task<int> M1Async()
                        {
                            WriteThread("Begin M1Async");
                            var i = await M2Async();
                            WriteThread("End M1Async");
                            return i + 1;
                        }
                
                        public static async Task<int> M2Async()
                        {
                            WriteThread("At M2Async");
                            return -1;
                        }
                
                        public static void WriteThread(string helpfultoken)
                        {
                            Console.WriteLine(helpfultoken
                                + " : " + Thread.CurrentThread.ManagedThreadId);
                        }
                    
                

💡 Co pojawi się w konsoli?

[Wszędzie będzie 1]

Async i Await : Stan zadania decyduje czy nowy wątek zostanie stworzony

                    
                        static async Task Main()
                        {
                            WriteThread("Begin Main");
                            var a = await M1Async();
                            WriteThread("Middle Main");
                            var b = await M1Async();
                            Console.WriteLine(a + b);
                            WriteThread("End Main");
                            Console.ReadKey();
                        }
                
                        public static async Task<int> M1Async()
                        {
                            WriteThread("Begin M1Async");
                            await Task.Delay(2000);
                            WriteThread("End M1Async");
                            return 0;
                        }
                
                        public static void WriteThread(string helpfultoken)
                        {
                            Console.WriteLine(helpfultoken
                                + " : " + Thread.CurrentThread.ManagedThreadId);
                        }
                    
                

💡 Co pojawi się w konsoli?

1,1,4,4,4,4

Przykład prawdziwego kodu async i await

                    
                        var fact = await GetCatFactAsync(new HttpClient());
                        Console.WriteLine(fact.Fact);

                        async Task<CatFact> GetCatFactAsync(HttpClient client)
                        {
                            var response = await client.GetAsync("https://catfact.ninja/fact");
                            response.EnsureSuccessStatusCode();

                            var stream = await response.Content.ReadAsStreamAsync();

                            JsonSerializerOptions options = new();
                            options.PropertyNameCaseInsensitive = true;
                            var catfact = await JsonSerializer.DeserializeAsync<CatFact>(stream, options);

                            return catfact ?? throw new InvalidOperationException("CatFact can't be null");
                        }

                        public record CatFact(string? Fact, int Length);
                    
                
blog
CW

Async i Await : maszyna stanów pamięta zakończenie

                    
                        Task<string> t = File.ReadAllTextAsync
                            (@"D:\numbers.txt");

                        var numbers1 = await t;
                        var numbers2 = await t;
                        
                        Console.WriteLine
                        (object.ReferenceEquals(numbers1, numbers2));
                    
                
blog
CW

O co chodzi ?

                    
                        Console.WriteLine(DateTime.Now);

                        // This block takes 1 second to run because all
                        // 5 tasks are running simultaneously
                        {
                            var a = Task.Delay(1000);
                            var b = Task.Delay(1000);
                            var c = Task.Delay(1000);
                            var d = Task.Delay(1000);
                            var e = Task.Delay(1000);
                        
                            await a;
                            await b;
                            await c;
                            await d;
                            await e;
                        }
                        
                        Console.WriteLine(DateTime.Now);
                        
                        // This block takes 5 seconds to run because each "await"
                        // pauses the code until the task finishes
                        {
                            await Task.Delay(1000);
                            await Task.Delay(1000);
                            await Task.Delay(1000);
                            await Task.Delay(1000);
                            await Task.Delay(1000);
                        }
                        Console.WriteLine(DateTime.Now);
                    
                
blog
CW

Hipoteza jak to działa

                    
                        static async Task Main()
                        {
                            Task<int> awaitableType = RandomNumberAsync();
                        
                            var awaiter = awaitableType.GetAwaiter();
                        
                            if (!awaiter.IsCompleted)
                            {
                                //robisz operacje która nie jest związana
                                //z zakończeniem tego zadania
                                //coś może działać w tle
                                //To symulje kod przed wyrażeniem await
                            }
                        
                            int a = awaiter.GetResult();
                            //kod po await
                        
                            //ten kod jest synchroniczny gdyż nie jest on pakowany przez maszynę stanów
                        }
                        
                        private static Task<int> RandomNumberAsync()
                        {
                            return Task.FromResult(4);
                        }
                    
                
blog
CW

Maszyna stanów async i await

                    
                        static async Task BarAsync()
                        {
                            Console.WriteLine("This happens before await");
                        
                            int i = await QuxAsync();
                        
                            Console.WriteLine("This happens after await. The result of await is " + i);
                        }
                    
                
blog
CW

Maszyna stanów async i await

                    
                        private static Task BarAsync()
                        {
                          Program.<BarAsync>d__2 stateMachine;
                          stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
                          stateMachine.<>1__state = -1;
                          stateMachine.<>t__builder.Start<Program.<BarAsync>d__2>(ref stateMachine);
                          return stateMachine.<>t__builder.Task;
                        }
                    
                

Maszyna stanów async i await

                    
                        private struct <BarAsync>d__2 : IAsyncStateMachine
                        {
                          public int <>1__state;
                          public AsyncTaskMethodBuilder <>t__builder;
                          private TaskAwaiter<int> <>u__1;
                        
                          void IAsyncStateMachine.MoveNext()
                          {
                            	int num1 = this.<>1__state;
                            	try
                            	{
                            	  TaskAwaiter<int> awaiter;
                            	  int num2;
                            	  if (num1 != 0)
                            	  {
                            		Console.WriteLine("This happens before await");
                            		awaiter = Program.QuxAsync().GetAwaiter();
                            		if (!awaiter.IsCompleted)
                            		{
                            		  this.<>1__state = num2 = 0;
                            		  this.<>u__1 = awaiter;
                            		  this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, Program.<BarAsync>d__2>(ref awaiter, ref this);
                            		  return;
                            		}
                            	  }
                            	  else
                            	  {
                            		awaiter = this.<>u__1;
                            		this.<>u__1 = new TaskAwaiter<int>();
                            		this.<>1__state = num2 = -1;
                            	  }
                            	  Console.WriteLine("This happens after await. The result of await is " + (object) awaiter.GetResult());
                            	}
                            	catch (Exception ex)
                            	{
                            	  this.<>1__state = -2;
                            	  this.<>t__builder.SetException(ex);
                            	  return;
                            	}
                            	this.<>1__state = -2;
                            	this.<>t__builder.SetResult();
                          }
                        
                          [DebuggerHidden]
                          void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
                          {
                            	this.<>t__builder.SetStateMachine(stateMachine);
                          }
                        }
                    
                
blog
Copyright © Cezary Walenciuk
blog
CW
blog
CW

Konstruktor w Task to red herring

                    

                        static async Task Main()
                        {
                            Task t = What();
                            await t;
                
                            var s = t.Status;
                        }
                
                        static Task What()
                        {
                            return new Task(
                                () => Console.WriteLine("1"));
                
                        }
                    
                

Poprawne użycie Task z async, aby wykonać długie zadanie które pierwotnie jest synchroniczne

                    
                        await Task.Run
                        (
                            () => DoExpensiveOperation(someParameter)
                        );
                    
                
blog
CW

Async Void w zdarzeniach w WPF

                    
                        private async void Button_Click(object sender, RoutedEventArgs e)
                        {
                            txtBlock.Text += "Rozpoczynam liczenie";
                        
                            var result = await HeavyCalculationAsync(txt_X.Text, txt_Y.Text);
                        
                            txtBlock.Text += result;
                        }                              
                    
                
blog
CW

Async Void w konstruktorze

                    
                        public class MyViewModel
                        {
                            public MyViewModel()
                            {
                                await RefreshData(CancellationToken.None);
                            }
                        
                            async Task RefreshData(CancellationToken ct)
                            {
                                await Task.Delay(100);
                            }
                        }                              
                    
                
blog
CW
blog
CW
blog
CW
blog
CW

Async Void i FireAndForgetSafeAsync

                    











                        public class MyViewModel
                        {
                            public MyViewModel()
                            {
                                RefreshData(CancellationToken.None)
                                    .FireAndForgetSafeAsync();
                        
                            }
                        
                            async Task RefreshData(CancellationToken ct)
                            {
                                await Task.Delay(100);
                            }
                        }
                        
                        public static class TaskUtilities
                        {                       
                            public static async void FireAndForgetSafeAsync(this Task task, 
                                Action<Exception> handler = null)
                            {
                                try
                                {
                                    await task;
                                }
                                catch (Exception ex)
                                {
                                    handler?.Invoke(ex);
                                }
                            }
                        }                      
                    
                
blog
CW

Async Void

                    
                        public class MyViewModel
                        {
                            public MyViewModel()
                            {
                                RefreshData(CancellationToken.None)
                                    .SafeFireAndForget((e) => Console.WriteLine(e));
                        
                            }
                        
                            async Task RefreshData(CancellationToken ct)
                            {
                                await Task.Delay(100);
                            }
                        }
                                                   
                    
                

Async Void w Lambda - źle

                    
                        var _ = 
                            async () => 
                                { await ProcessData(); }
                    
                

Async Void w Lambda - dobrze

                    
                        var _ = 
                            async () => await ProcessData(); 
                    
                
blog
CW
blog
CW

Nie wszystko trzeba awaitować

                    
                        private async Task StartAsync()
                        {
                            await InternalAsync1();
                        }
                        
                        private async Task InternalAsync1()
                        {
                            await InternalAsync2();
                        }
                        
                        private async Task InternalAsync2()
                        {
                            await InternalAsync3();
                        }
                        
                        private async Task InternalAsync3()
                        {
                            await Task.Delay(1000);
                        }                             
                    
                

Przykład techniki async eliding

                    
                        private async Task StartAsync()
                        {
                            await InternalAsync1();
                        }
                        
                        private async Task InternalAsync1()
                        {
                            return InternalAsync2();
                        }
                        
                        private Task InternalAsync2()
                        {
                            return InternalAsync3();
                        }
                        
                        private Task InternalAsync3()
                        {
                            return Task.Delay(1000);
                        }                             
                    
                

Eliding Async and Await : PROBLEM

                    
                        public async Task<string> GetWithKeywordsAsync(string url)
                        {
                            using (var client = new HttpClient())
                                return await client.GetStringAsync(url);
                        }
                            
                        public Task<string> GetElidingKeywordsAsync(string url)
                        {
                            using (var client = new HttpClient())
                                return client.GetStringAsync(url);
                        }
                    
                

blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW

.GetAwaiter().GetResult();

                    
                        
                        // Metoda synchronizująca wywołanie FetchDataAsync
                        public void FetchDataSynchronously()
                        {
                            try
                            {
                                // Wywołanie metody asynchronicznej w sposób synchroniczny
                                string result = FetchDataAsync().GetAwaiter().GetResult();
                                Console.WriteLine(result);
                            }
                            catch (Exception ex)
                            {
                                // Bezpośrednia obsługa wyjątków
                                Console.WriteLine($"Wystąpił błąd: {ex.Message}");
                            }
                        }

                        // Asynchroniczna metoda pobierająca dane
                        public async Task FetchDataAsync()
                        {
                            using (HttpClient client = new HttpClient())
                            {
                                HttpResponseMessage response = 
                                    await client.GetAsync("https://jsonplaceholder.typicode.com/posts/1");
                                response.EnsureSuccessStatusCode();
                                return await response.Content.ReadAsStringAsync();
                            }
                        }
                    
                
blog
CW

Deadlock

                    

                        private readonly static string _url = "https://postman-echo.com/delay/10";

                        private void Button_Click(object sender, RoutedEventArgs e)
                        {
                            ResultTextBlock.Text = "";
                            ResultTextBlock.Text += "Przed MyGetStringAsync()";
                            var result = MyGetStringAsync().Result;
                            ResultTextBlock.Text += "Napisze coś w międzyczasie";
                            ResultTextBlock.Text = result;
                            ResultTextBlock.Text += "Po MyGetStringAsync()";
                        }
                        
                        public async Task<string> MyGetStringAsync()
                        {
                            using (HttpClient httpClient = new HttpClient())
                            {
                                return await httpClient.GetStringAsync(_url);
                            }
                        }
                    
                
blog
CW

ConfigureAwait(false);

                    
                        
                        private readonly static string _url = "https://postman-echo.com/delay/10";

                        private void Button_Click(object sender, RoutedEventArgs e)
                        {
                            ResultTextBlock.Text = "";
                            ResultTextBlock.Text += "Przed MyGetStringAsync()";
                            var result = MyGetStringAsync().Result;
                            ResultTextBlock.Text += "Napisze coś w międzyczasie";
                            ResultTextBlock.Text = result;
                            ResultTextBlock.Text += "Po MyGetStringAsync()";
                        }
                        
                        public async Task<string> MyGetStringAsync()
                        {
                            using (HttpClient httpClient = new HttpClient())
                            {
                                return await httpClient.GetStringAsync(_url)
                                    .ConfigureAwait(false);
                            }
                        }
                    
                
blog
CW

ConfigureAwait co właściwie robi

                    
                        private async Task DoAsync()
                        {
                            txtBlock.Text += "Rozpoczynam liczenie";
                            string result;
                        
                            try
                            {
                                result = await HeavyCalculationAsync(txt_X.Text, txt_Y.Text)
                                    .ConfigureAwait(true);
                        
                               await SaveToFile(result).ConfigureAwait(false);
                            }
                            catch (Exception ex)
                            {
                                result = ex.ToString();
                            }
                        
                            txtBlock.Text += result;
                        }                          
                    
                
blog
CW
blog
CW

ConfigureAwait w .NET 8

                    
                        private async Task DoAsync()
                        {
                            txtBlock.Text += "Rozpoczynam liczenie";
                            string result;
                        
                            try
                            {
                                result = await HeavyCalculationAsync(txt_X.Text, txt_Y.Text)
                                    .ConfigureAwait(ConfigureAwaitOptions.ContinueOnCapturedContext);
                        
                               await SaveToFile(result).ConfigureAwait(ConfigureAwaitOptions.None
                                   | ConfigureAwaitOptions.SuppressThrowing
                                   | ConfigureAwaitOptions.ForceYielding);
                            }
                            catch (Exception ex)
                            {
                                result = ex.ToString();
                            }
                        
                            txtBlock.Text += result;
                        }                       
                    
                
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW

ThrowIfCancellationRequested

                    
                        private Task ForeverLong(CancellationToken ct)
                        {
                            while (true)
                            {
                                ct.ThrowIfCancellationRequested();
                        
                                //Cheking 
                        
                                Task.Delay(1000);
                            }
                        }                                         
                    
                
blog
CW

WaitAsync

                    

                        public async Task DoWorkAsync()
                        {
                            // Symulacja długotrwałej operacji
                            await Task.Delay(5000);
                            Console.WriteLine("Operacja zakończona.");
                        }
                    
                        public async Task Run()
                        {
                            using var cts = new CancellationTokenSource();
                            Task mytask = DoWorkAsync();
                    
                            // Anuluj zadanie po 2 sekundach
                            cts.CancelAfter(2000);
                    
                            try
                            {
                                // Użycie WaitAsync, aby dodać możliwość anulowania
                                await mytask.WaitAsync(cts.Token);
                            }
                            catch (OperationCanceledException)
                            {
                                Console.WriteLine("Zadanie zostało anulowane.");
                            }
                        }                                      
                    
                
blog
CW

Task IEnumerable przykład użycia

                    
                        private async Task<IEnumerable<string>> GeMyPoem2()
                        {
                            using var stream = new StreamReader(@"D:\p.txt");
                        
                            List<string> s = new();
                            while (await stream.ReadLineAsync() is string line)
                            {
                                await Task.Delay(500);
                        
                                s.Add(line);
                            }
                        
                            return s;
                        }                  
                    
                

IAsyncEnumerable przykład użycia

                    
                        private async IAsyncEnumerable<string> GeMyPoem()
                        {
                            using var stream = new StreamReader(@"D:\p.txt");
                        
                            while (await stream.ReadLineAsync() is string line)
                            {
                                await Task.Delay(500);
                        
                                yield return line;
                            }
                        }                     
                    
                

IAsyncEnumerable przykład użycia

                    
                        private async IAsyncEnumerable<string> GeMyPoem()
                        {
                            using var stream = new StreamReader(@"D:\p.txt");
                        
                            while (await stream.ReadLineAsync() is string line)
                            {
                                await Task.Delay(500);
                        
                                yield return line;
                            }
                        }                     
                    
                

IAsyncEnumerable przykład użycia

                    
                        private async void Button_Click(object sender, RoutedEventArgs e)
                        {
                            await Read();
                        }
                    
                        private async Task Read()
                        {
                            txtBlock.Text = "";
                            await foreach (var item in GeMyPoem())
                            {
                                txtBlock.Text += item + "\n";
                            }
                        }                
                    
                
blog
CW
blog
CW

new ValueTask

                    
                        public Task GetCustomerIdAsync()
                        {
                            return Task.FromResult(1);
                        }

                        public ValueTask GetCustomerIdAsync()
                        {
                            return new ValueTask(1);
                        }
                    
                

new ValueTask

                    
                        // WARNING
                        ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
                        ... // storing the instance into a local makes it much more likely it'll be misused,
                            // but it could still be ok
                        
                        // BAD: awaits multiple times
                        ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
                        int result = await vt;
                        int result2 = await vt;
                        
                        // BAD: awaits concurrently (and, by definition then, multiple times)
                        ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
                        Task.Run(async () => await vt);
                        Task.Run(async () => await vt);
                        
                        // BAD: uses GetAwaiter().GetResult() when it's not known to be done
                        ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
                        int result = vt.GetAwaiter().GetResult();
                    
                
blog
CW

Parallel.ForEach

                    
                        var numbers = new List<int> { 1, 2, 3, 4, 5 };

                        Parallel.ForEach(numbers, number =>
                        {
                            Console.WriteLine($"Processing {number} on thread {Thread.CurrentThread.ManagedThreadId}");
                            Thread.Sleep(1000); // Symulacja pracy CPU
                        });
                    
                

Task.WhenAll

                    
                        
                        
                        

                        var urls = new List<string>
                        {
                            "https://cezarywalenciuk.pl",
                            "https://www.instagram.com/",
                            "https://www.youtube.com/"
                        };
                        
                        var tasks = new List<Task>();
                        
                        using HttpClient client = new HttpClient();
                        
                        foreach (var url in urls)
                        {
                            tasks.Add(FetchUrlAsync(client, url));
                        }
                        
                        await Task.WhenAll(tasks);

                        static async Task FetchUrlAsync(HttpClient client, string url)
                        {
                            Console.WriteLine($"Fetching {url}");
                            string result = await client.GetStringAsync(url);
                            Console.WriteLine($"Finished fetching {url}: {result.Length} characters.");
                        }
                    
                

Async Void w Lambda - źle

                    
                        
                        
                        




















                        




                        using System.Diagnostics;

                        var ids = Enumerable.Range(1, 10).ToList();
                        
                        await WhenAll(ids);
                        
                        await ParallelForeach(ids);
                        
                        async Task Process(int id)
                        {
                            await Task.Delay(2000);
                            Console.WriteLine($"Processed id: {id}");
                        }
                        
                        // Good option for batches of I/O that 
                        // WON'T throttle/limit the destination (api, db, etc).
                        async Task WhenAll(List<int> ids)
                        {
                            Console.WriteLine("Before WhenAll: " + DateTime.Now.ToLongTimeString());
                            Stopwatch s = Stopwatch.StartNew();

                            await Task.WhenAll(ids.Select(id => Process(id)));

                            stopwatch.Stop();
                            Console.WriteLine("After WhenAll: " + DateTime.Now.ToLongTimeString());
                            Console.WriteLine
                            ($"Elapsed Time: {s.Elapsed.TotalSeconds:F2} seconds, {s.ElapsedMilliseconds} ms");
                        }
                        
                        // Good option for processing that COULD throttle/limit the destination
                        // Also good for light/medium CPU bound in small batches
                        async Task ParallelForeach(List<int> ids)
                        {
                            // Parallel.ForEach
                            Console.WriteLine("Before ParallelForeach: " + DateTime.Now.ToLongTimeString());
                            ParallelOptions options = new()
                            {
                                MaxDegreeOfParallelism = 2
                            };
                        
                            Stopwatch s = Stopwatch.StartNew();
                            await Parallel.ForEachAsync(ids, options, async (id, token) =>
                            {
                                await Process(id);
                            });

                            stopwatch.Stop();
                            Console.WriteLine("After ParallelForeach: " + DateTime.Now.ToLongTimeString());
                            Console.WriteLine
                            ($"Elapsed Time: {s.Elapsed.TotalSeconds:F2} seconds, {s.ElapsedMilliseconds} ms");
                        }
                    
                
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW
blog
CW

Problem przetwarzania tasków jak tylko się skończą

                    
                        List<Task<int>> tasks = Enumerable.Range(1,5).Select(Calculate).ToList();
                        
                        // Process tasks as they complete
                        while (downloadTasks.Any())
                        {
                            Task<int> finishedTask = await Task.WhenAny(tasks);
                            tasks.Remove(finishedTask);
                            int result = await finishedTask;
                            //ProcessDataChunk(result);
                            Console.WriteLine(result);
                        }                                       
                    
                

Na pomoc przychodzi WhenEach

                    
                        namespace System.Threading.Tasks;

                        public class Task
                        {
                           public static IAsyncEnumerable<Task> WhenEach(params Task[] tasks);
                           public static IAsyncEnumerable<Task> WhenEach(params ReadOnlySpan<Task> tasks); // params when possible
                           public static IAsyncEnumerable<Task> WhenEach(IEnumerable<Task> tasks);
                        }                                    
                    
                

Na pomoc przychodzi WhenEach

                    
                        using System.Diagnostics;

                        var tasks = Enumerable.Range(1, 20).Select(DoSomeTask);
                        
                        await foreach (var task in Task.WhenEach(tasks))
                        {
                            var result = await task;
                            Console.WriteLine($"{result.TaskName} is done, took {result.ExecutionTime.TotalMilliseconds}ms");
                        }
                        
                        static async Task<(string TaskName, TimeSpan ExecutionTime)> DoSomeTask(int taskIndex)
                        {
                            var stopwatch = Stopwatch.StartNew();
                            await Task.Delay(Random.Shared.Next(100, 2500));
                            stopwatch.Stop();
                            return ($"Task {taskIndex}", stopwatch.Elapsed);
                        }                 
                    
                

Na pomoc przychodzi WhenEach

                    
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        



                        
                        
                        
                        
                        var cts = new CancellationTokenSource();
                        cts.CancelAfter(2500);
                        // Anuluj zadania po 2.5 sekundy
                        
                        var tasks = new List<Task<int>>
                                {
                                    DoWork(1, 1000,cts.Token),
                                    DoWork(2, 1500,cts.Token),
                                    DoWork(3, 3000,cts.Token)
                                };
                        
                        try
                        {
                            await foreach (var task in Task.WhenEach(tasks))
                            {
                                var result = await task;
                                Console.WriteLine($"{result} is done");
                            }
                        
                        }
                        catch (OperationCanceledException)
                        {
                            Console.WriteLine("Operacja została anulowana.");
                        }
                        
                        
                        static async Task<int> DoWork(int id, int delay, 
                            CancellationToken cancellationToken)
                        {
                            await Task.Delay(delay / 2);
                        
                            if (cancellationToken.IsCancellationRequested)
                            {
                                Console.WriteLine("Anulowanie operacji...Przy 1 sprawdzeniu");
                                cancellationToken.ThrowIfCancellationRequested();
                            }
                        
                            await Task.Delay(delay / 2);
                        
                            if (cancellationToken.IsCancellationRequested)
                            {
                                Console.WriteLine("Anulowanie operacji...Przy 2 sprawdzeniu");
                                cancellationToken.ThrowIfCancellationRequested();
                            }
                        
                            Console.WriteLine($"Zadanie {id} zakończone.");
                            return id;
                        }
                        
                                               
                    
                
blog
CW