C# language feature:

  • Asynchronous methods

applies to

  • threading model, program responsiveness

benefit

  • Allows through a clean api asynchronous functions i.e. delegation of the “timing of code execution” to the OS (Operating System) aka Promise Model of Concurrency.

how to use

  • 1) Use async to designate asynchronous method.
  • 2) Asynchronous methods must return one of the following: Task, void (send and forget) and Task<T> (if object/data needed to proceed).
  • 3) Use await within the asynchronous method to “yield control” to the caller. i.e. caller execution continues. The asynchronous method execution will be “scheduled” to be completed by the OS.
  • 4) For CPU-bound code use Task.Run (spawn of seperate thread), for I/O-bound code DO NOT USE Task.Run.

important

  • Add “Async” as the suffix to designate asynchronous methods. To yeild control to caller, async methods must have await keyword. Only use async void for event handlers. Calling code awaiting tasks should be “non-blocking”. Asynchronous code should minimize dependencies on state.

caution

  • Actual results of asynchronous programming using the above recommendations may differ depending on a particular situation. For example CPU-bound code execution may not be as “expensive” as the overhead of spawning a thread. Additionally, in a windows application spawned threads should never update the UI directly.

example:

        public class Asynchronous_methods_Example {
            // 1) modify method with async 2) return Task<T> (Object needed to proceed only if awaited on from caller)
            async Task<int> StartCPUTasksAsync()
            {
                System.Console.WriteLine("Before StartCPUTasks:");
                Random x = new Random();
                List<string> idList = new List<string> { "A", "B", "C", "D" };
                IEnumerable<Task> cpuTasks = idList.Select(id => CpuTaskAsync(id, x.Next(2000, 5000)));
                await Task.WhenAll(cpuTasks); // 3) starts all cpu tasks and yeild to caller of StartCPUTasksAsync
                System.Console.WriteLine("After StartCPUTasks:"); // note: await Task.WhenAll above prevents execution of this until all io tasks complete
                return idList.Count;
            }

            Task CpuTaskAsync(string taskid, int milliseconds = 1000)
            {
                // 4) use Task.Run to spawn thread for CPU bound task
                return Task.Run(() =>
                {
                    Console.WriteLine(taskid + ": (" + milliseconds + ") CpuTaskAsync start:" + System.DateTime.Now.ToString());
                    System.Threading.Thread.Sleep(milliseconds);
                    Console.WriteLine(taskid + ": CpuTaskAsync end:" + System.DateTime.Now.ToString());
                }
               );
            }

            // 1) modify method with async 2) return Task<T> (caller is able to force waiting for Object using await)
            async Task<int> StartIOTasksAsync()
            {
                System.Console.WriteLine("Before StartIOTasksAsync:");
                List<string> UriList = new List<string> { "https://microsoft.com", "http://apple.com", "https://code-sage.com" };
                IEnumerable<Task> ioTasks = UriList.Select(uri => IoTaskAsync(uri));
                await Task.WhenAll(ioTasks);  // starts all requests up to the "await GetByteArrayAsync statement in IoTaskAsync"
                System.Console.WriteLine("After StartIOTasksAsync:");  // note: await Task.WhenAll above prevents execution of this until all IoTaskAsync(s) complete
                return UriList.Count;
            }

            // 1) modify method with async that returns Task, void (send and forget)
            async Task IoTaskAsync(string URI)
            {
                System.Console.WriteLine("IoTaskAsync start:" + URI);
                HttpClient client = new HttpClient();
                byte[] response = await client.GetByteArrayAsync(URI);  // 2) yield control to caller (i.e. StartIOTasksAsync above starts next web request)
                System.Console.WriteLine("IoTaskAsync done:" + URI); // note: resumed by OS when response available (because of the await state above
            }

            // 1) modify method with async that returns Task, void (send and forget)
            public async void StartTasksNoBlockingAtAll()
            {
                System.Console.WriteLine("StartTasksNoBlockingAtAll start");
                StartCPUTasksAsync();  //note: without await this async method returns before all cputasks complete
                StartIOTasksAsync();  //note: without await this async method returns before all io tasks complete
                System.Console.WriteLine("StartTasksNoBlockingAtAll ends");  // this will displayed before either of the above "after cpu or io tasks" messages finish
            }
            // 1) modify method with async 2) return Task<T> (caller is able to force waiting for Object using await)
            public async Task<int> StartTasksinGroupsandWaitAsync()
            {
                System.Console.WriteLine("StartTasksinGroupsandWaitAsync start");
                int x = await StartCPUTasksAsync();  // note: this async will wait for all cputask initiated with this call
                int y = await StartIOTasksAsync();  //note: this async method returns before all io tasks complete
                System.Console.WriteLine("StartTasksinGroupsandWaitAsync ends");  // this will displayed before either of the above "after cpu or io tasks" messages finish
                return x+y;
            }
        }