Using async - await in different scenarios

While using asynchronous programming you might come across different scenarios. A few of those are explained here briefly.

  • Awaiting a task in Non Blocking manner

    For this you just have to await the async method that you want to run in non blocking manner. 
    public void Execute()
    {
        ExecuteAsync();
        Console.WriteLine("After calling ExecuteAsync");
        Console.ReadKey();
    }
    
    public async Task ExecuteAsync()
    {
        Task t = Task.Run(() => {
            long sum = 0;
            int n = 5000000;
            for (int i = 1; i <= n; i++)
            {
                sum += i;
            }
            Console.WriteLine("Sum is : {0}", sum);
        });
    
        await t;
        Console.WriteLine("Task completed");
    }
    

    The output of above code on calling Execute() is:-
    After calling ExecuteAsync
    Sum is : 12500002500000
    Task completed

  • Awaiting a task in Blocking manner

    While using async & await you might come across situation where you don't want to call an async method asynchronously, that is you want to wait for the task to complete before proceeding to further execution. In that case you can either use Task.Wait method or Task.Result property. Each of these are explained below:-

    1. Task.Wait : It makes the calling thread to wait until the task is complete, that is it blocks further execution on the calling thread until the task is completed. It has other overloads as well in which you can specify the time duration for which the calling thread waits for the asynchronous operation to complete.
      public void Execute()
      {
          ExecuteAsync();
          Console.WriteLine("After calling ExecuteAsync");
          Console.ReadKey();
      }
      
      public async Task ExecuteAsync()
      {
          Task t = Task.Run(() => {
              long sum = 0;
              int n = 5000000;
              for (int i = 1; i <= n; i++)
              {
                  sum += i;
              }
              Console.WriteLine("Sum is : {0}", sum);
          });
      
          t.Wait();
          Console.WriteLine("Task completed");
      }
      

      The output of above code on calling Execute() is:-
      Sum is : 12500002500000
      Task completed
      After calling ExecuteAsync

    2. Task.Result : It can only be used with Task<TResult>. It is mainly used to get the result value of a task but getting the value using Task<TResult>.Result property blocks the calling thread until the task is complete which is same as calling the Task.Wait method.
      public void Execute()
      {
          ExecuteAsync();
          Console.WriteLine("After calling ExecuteAsync");
          Console.ReadKey();
      }
      
      public async Task ExecuteAsync()
      {
          Task<long> t = Task.Run(() =>
          {
              long sum = 0;
              int n = 5000000;
              for (int i = 1; i <= n; i++)
              {
                  sum += i;
              }
              Console.WriteLine("Sum is : {0}", sum);
              return sum;
          });
      
          var s = t.Result;
          Console.WriteLine("Task completed");
      }
      

      The output of above code on calling Execute() is:-
      Sum is : 12500002500000
      Task completed
      After calling ExecuteAsync
      This output is same as the output with calling the Wait method.

  • Delay the execution of a task

    We can delay the operation of a task by a specified amount of time by using Task.Delay method. We can cause the time delay either at the beginning of a task or during the execution of a task. A typical example of delaying a task during execution is as shown below:-
    public async Task ExecuteAsync()
    {
        //some code
        await Task.Delay(2000);
        //some code
    }
    

  • Wait for all of the specified tasks to complete

    You might sometimes need to ensure that all of the specified tasks (more than one) are complete before proceeding further, in that case you can use await Task.WhenAll (non blocking) or Task.WaitAll (blocking). Both of them are demonstrated below:-

    1. Without using any of these methods, that is, without waiting for the tasks to complete :
      public void Execute()
      {
          ExecuteAsync();
          Console.WriteLine("After calling ExecuteAsync");
          Console.ReadKey();
      }
      
      public async Task ExecuteAsync()
      {
          Task t1 = Task.Run(async() = >
          {
              await Task.Delay(3000);
              Console.WriteLine("Task 't1' completed");
          });
      
          Task t2 = Task.Run(async() = >
          {
              await Task.Delay(1000);
              Console.WriteLine("Task 't2' completed");
          });
      
          Console.WriteLine("ExecuteAsync completed");
      }
      

      The output of above code on calling Execute() is:-
      ExecuteAsync completed
      After calling ExecuteAsync
      Task 't2' completed
      Task 't1' completed

    2. Using Task.WaitAll :
      public void Execute()
      {
          ExecuteAsync();
          Console.WriteLine("After calling ExecuteAsync");
          Console.ReadKey();
      }
      
      public async Task ExecuteAsync()
      {
          var tasks = new List<task>();
      
          Task t1 = Task.Run(async () =>
          {
              await Task.Delay(3000);
              Console.WriteLine("Task 't1' completed");
          });
      
          Task t2 = Task.Run(async () =>
          {
              await Task.Delay(1000);
              Console.WriteLine("Task 't2' completed");
          });
      
          tasks.Add(t1);
          tasks.Add(t2);
      
          try
          {
              // Wait for both t1 and t2 to complete.
              Task.WaitAll(tasks.ToArray());
          }
          catch (AggregateException e)
          {
              for (int j = 0; j < e.InnerExceptions.Count; j++)
              {
                  Console.WriteLine("\n\n{0}", e.InnerExceptions[j].ToString());
              }
          }
      
          Console.WriteLine("ExecuteAsync completed");
      }
      

      The output of above code on calling Execute() is:-
      Task 't2' completed
      Task 't1' completed
      ExecuteAsync completed
      After calling ExecuteAsync

    3. Using await Task.WhenAll :
      public void Execute()
      {
          ExecuteAsync();
          Console.WriteLine("After calling ExecuteAsync");
          Console.ReadKey();
      }
      
      public async Task ExecuteAsync()
      {
          var tasks = new List<task>();
      
          Task t1 = Task.Run(async () =>
          {
              await Task.Delay(3000);
          Console.WriteLine("Task 't1' completed");
          });
      
          Task t2 = Task.Run(async () =>
          {
              await Task.Delay(1000);
          Console.WriteLine("Task 't2' completed");
          });
      
          tasks.Add(t1);
          tasks.Add(t2);
      
          try
          {
              await Task.WhenAll(tasks.ToArray());
          }
          catch (AggregateException e)
          {
              for (int j = 0; j < e.InnerExceptions.Count; j++)
              {
                  Console.WriteLine("\n\n{0}", e.InnerExceptions[j].ToString());
              }
          }
      
          Console.WriteLine("ExecuteAsync completed");
      }
      

      The output of above code on calling Execute() is:-
      After calling ExecuteAsync
      Task 't2' completed
      Task 't1' completed
      ExecuteAsync completed

  • Proceed if any one of the specified tasks completes

    There might be situation where you want to proceed to further execution if any one of the specified task completes its operation. Like most of the cases mentioned above there are two ways of doing this as well. One is using Task.WaitAny (blocking) the other is await Task.WhenAny (non blocking). Taking the same example used above these two methods are demonstrated as:-

    1. Task.WaitAny :
      public void Execute()
      {
          ExecuteAsync();
          Console.WriteLine("After calling ExecuteAsync");
          Console.ReadKey();
      }
      
      public async Task ExecuteAsync()
      {
          var tasks = new List<task>();
      
          Task t1 = Task.Run(async () =>
          {
              await Task.Delay(3000);
              Console.WriteLine("Task 't1' completed");
          });
      
          Task t2 = Task.Run(async () =>
          {
              await Task.Delay(1000);
              Console.WriteLine("Task 't2' completed");
          });
      
          tasks.Add(t1);
          tasks.Add(t2);
      
          try
          {
              Task.WaitAny(tasks.ToArray());
          }
          catch (AggregateException e)
          {
              for (int j = 0; j < e.InnerExceptions.Count; j++)
              {
                  Console.WriteLine("\n\n{0}", e.InnerExceptions[j].ToString());
              }
          }
      
          Console.WriteLine("ExecuteAsync completed");
      }
      

      The output of above code on calling Execute() is:-
      Task 't2' completed
      ExecuteAsync completed
      After calling ExecuteAsync
      Task 't1' completed

    2. await Task.WhenAny :
      public void Execute()
      {
          ExecuteAsync();
          Console.WriteLine("After calling ExecuteAsync");
          Console.ReadKey();
      }
      
      public async Task ExecuteAsync()
      {
          var tasks = new List<task>();
      
          Task t1 = Task.Run(async () =>
          {
              await Task.Delay(3000);
              Console.WriteLine("Task 't1' completed");
          });
      
          Task t2 = Task.Run(async () =>
          {
              await Task.Delay(1000);
              Console.WriteLine("Task 't2' completed");
          });
      
          tasks.Add(t1);
          tasks.Add(t2);
      
          try
          {
              await Task.WhenAny(tasks.ToArray());
          }
          catch (AggregateException e)
          {
              for (int j = 0; j < e.InnerExceptions.Count; j++)
              {
                  Console.WriteLine("\n\n{0}", e.InnerExceptions[j].ToString());
              }
          }
      
          Console.WriteLine("ExecuteAsync completed");
      }
      

      The output of above code on calling Execute() is:-
      After calling ExecuteAsync
      Task 't2' completed
      ExecuteAsync completed
      Task 't1' completed

  • Execute another task when a Task completes

    If you want to execute another task when a task completes its execution then you can use the Task.ContinueWith method. A simple example is shown below :-

    public void Execute()
    {
        ExecuteAsync();
        Console.WriteLine("After calling ExecuteAsync");
        Console.ReadKey();
    }
    
    public async Task ExecuteAsync()
    {
        Task t1 = Task.Run(async () =>
        {
            await Task.Delay(3000);
            Console.WriteLine("Task 't1' completed");
        });
    
        Task continuationTask  =  t1.ContinueWith(_ => {
            Console.WriteLine("Task 't2' completed");
        });
    
    
        await continuationTask;
    
        Console.WriteLine("ExecuteAsync completed");
    }
    

    The output of above code on calling Execute() is:-
    After calling ExecuteAsync
    Task 't1' completed
    Task 't2' completed
    ExecuteAsync completed

No comments:

Post a Comment