[Reading Notes] C# Advanced Programming Chapter 13 Asynchronous Programming

Keywords: C# Programming Attribute Windows REST

(1) Importance of asynchronous programming

With asynchronous programming, method calls run in the background (usually with the help of threads or tasks) and do not block the calling thread. There are three different asynchronous programming modes: asynchronous mode, Event-based Asynchronous Mode and newly added task-based asynchronous mode (TAP), which can be implemented by using async and await keywords.

 

 

(2) Asynchronous mode

1. The Asynchronous Programming Model of APM in C#1.

2. Event-based Asynchronous Pattern (EAP) in C#2.

3. Task-based Asynchronous Pattern (TAP).

Reference resources: http://www.cnblogs.com/zhaopei/p/async_one.html

 

 

(3) Foundation of Asynchronous Programming

The async and await keywords are just compiler functions. The compiler creates code with the Task class.

 

1. Creating tasks

 1 /// <summary>
 2 /// Synchronization method
 3 /// </summary>
 4 /// <returns></returns>
 5 static string SayHi(string name)
 6 {
 7     Thread.Sleep(3000);
 8     return "Hello!"+ name;
 9 }
10 
11 /// <summary>
12 /// Task-based Asynchronous Mode
13 /// </summary>
14 /// <returns></returns>
15 static Task<string> SayHiAsync(string name)
16 {
17     return Task.Run<string>(()=> {
18         return SayHi(name);
19     });
20 }

Generic version of Task.Run<string> creates a task that returns a string.

 

2. Calling asynchronous methods

Using await keywords requires a method declared with an async modifier. Before the await method is completed, the other code in the method will not continue to execute, but the thread calling the await method will not be blocked.

private async static void CallerWithAsync()
{
    string result = await SayHiAsync("Zhang San");
    Console.WriteLine(result);
}

The async modifier can only be used to return Task and void methods.

 

3. Continuation of tasks

The ContinueWith method of the Task class defines the code that is called when the task is completed. The delegate assigned to the ContinueWith method accepts the completed task as a parameter, and uses the Result attribute to access the results returned by the task.

private static void CallerWithContinuationTask()
{
    Task<string> t = SayHiAsync("Li Si");
    t.ContinueWith(_t => Console.WriteLine(_t.Result));
}

 

4. Synchronization context

The WPF application sets the Dispatcher Synchronization Context property, and the Windows Form application sets the Windows Forms Synchronization Context property. If the thread calling the asynchronous method is allocated to the synchronous context, await will continue to execute after completion. If you do not use the same context, you must call ConfigureAwait(ContinueOnCapturedContext:false) of the Task class.

 

5. Using multiple asynchronous methods

(1) Calling asynchronous methods in sequence

private async static void MultipleCallerWithAsync()
{
    string result1 = await SayHiAsync("Zhang San");
    string result2 = await SayHiAsync("Li Si");
    Console.WriteLine("Completed two greetings!{0} and {1}", result1, result2);
}

 

(2) Use combiners

A combiner can accept multiple parameters of the same type and return values of the same type. Task combiner accepts multiple Task objects as parameters and returns a Task.

private async static void MultipleCallerWithAsyncWithCombinators1()
{
    Task<string> task1 =  SayHiAsync("Zhang San");
    Task<string> task2 =  SayHiAsync("Li Si");
    await Task.WhenAll(task1,task2);
    Console.WriteLine("Completed two greetings!{0} and {1}", result1, result2);
}

The Task class defines WhenAll and WhenAny combiners.

When the task return type is the same, the return result can be accepted with an array.

private async static void MultipleCallerWithAsyncWithCombinators2()
{
    Task<string> task1 =  SayHiAsync("Zhang San");
    Task<string> task2 =  SayHiAsync("Li Si");
    string [] results=await Task.WhenAll(task1,task2);
    Console.WriteLine("Completed two greetings!{0} and {1}", results[0], results[1]);
}

 

6. Conversion of asynchronous mode

When some classes do not provide a task-based asynchronous pattern (only BeginXX,EndXX), you can use the FromAsync method defined by the TaskFactory class to convert it to a task-based asynchronous pattern.

private static async void ConvertingAsyncPattern()
{
    Func<string, string> method = SayHi;
    string result = await Task<string>.Factory.FromAsync<string>((name, callback,state) => {
        return method.BeginInvoke(name, callback, state);
    },ar=> {
        return method.EndInvoke(ar);
    },"Wang Ma Zi",null);
Console.WriteLine(result);
}

 

 

(4) Error handling

1. Exception handling of asynchronous methods

A better way to handle asynchronous method exceptions is to use the await keyword and place it in try/catch statements.

 1 static async Task ThrowAfter(int ms, string message)
 2 {
 3     await Task.Delay(ms);
 4     throw new Exception(message);
 5 }
 6 
 7 private static async void HandleOneError()
 8 {
 9     try
10     {
11         await ThrowAfter(2000, "first");
12     }
13     catch (Exception ex)
14     {
15         Console.WriteLine("handled {0}", ex.Message);
16     }
17 }

 

2. Abnormal handling of multiple asynchronous methods

When calling two or more methods that throw exceptions sequentially, the above methods should not be used again, because the rest of the methods in the try block will not be called when the first asynchronous method throws exceptions.

If you need to continue executing the remaining methods and then handle exceptions, you can use the Task.WhenAll method, so that no matter whether the task throws an exception or not, it will wait until all tasks are finished. However, only the first exception passed to WhenAll method can be seen.

private static async void HandleOneError()
{
    try
    {
        Task task1 = ThrowAfter(1000, "first");
        Task task2 = ThrowAfter(2000, "second");
        await Task.WhenAll(task1, task2);
    }
    catch (Exception ex)
    {
        Console.WriteLine("handled {0}", ex.Message);
    }
}

If you need to finish executing the remaining methods and get all thrown exceptions, you can declare task variables outside the try block so that they can be accessed within the catch block. This allows you to use the IsFaulted property to check the status of the task, and when true, you can use Exception.InnerException of the Task class to access exception information.

private static async void HandleOneError()
{
    Task task1 = null;
    Task task2 = null;
    try
    {
        task1 = ThrowAfter(1000, "first");
        task2 = ThrowAfter(2000, "second");
        await Task.WhenAll(task1, task2);
    }
    catch (Exception ex)
    {
        if (task1 != null && task1.IsFaulted)
        {
            Console.WriteLine(task1.Exception.InnerException);
        }
        if (task2 != null && task2.IsFaulted)
        {
            Console.WriteLine(task2.Exception.InnerException);
        }
    }
}

 

3. Use Aggregate Exception information

To get all the exception information, you can also write the results returned by Task.WhenAll into a Task variable, and then access the Exception attribute of the Task class (AggregateException type). AggregateException defines the InnerExceptions attribute, which contains all the exception information.

private static async void HandleOneError()
{
    Task task = null;
    try
    {
        Task task1 = ThrowAfter(1000, "first");
        Task task2 = ThrowAfter(2000, "second");
        await (task = Task.WhenAll(task1, task2));
    }
    catch (Exception)
    {
        foreach (var exception in task.Exception.InnerExceptions)
        {
            Console.WriteLine(exception);
        }
    }
}

 

 

(5) Cancellation

1. Canceling Tasks

private CancellationTokenSource cts;
private void OnCancel()
{
    if (cts != null)
    {
        cts.Cancel();
        //cts.CancelAfter(1000);//Waiting 1000 ms Later Cancellation
    }
}

 

2. Canceling Tasks Using Framework Features

private async void OnTaskBasedAsyncPattern()
{
    List<string> urlList = new List<string>();
    urlList.Add("http://www.baidu.com");
    cts = new CancellationTokenSource();
    try
    {
        foreach (var url in urlList)
        {
            Random rd = new Random();
            int i = rd.Next(1, 100); //1 Number between 100,
            if (i%2==0)
            {
                OnCancel();//Cancel tasks when random numbers are even
            }
            var client = new HttpClient();
            var response = await client.GetAsync(url, cts.Token);//GetAsync The method checks whether the operation should be cancelled
            var result =await response.Content.ReadAsStringAsync();
            Console.WriteLine(result);
        }
    }
    catch (OperationCanceledException ex)//The exception is thrown when the task is cancelled
    {
        Console.WriteLine(ex.Message);
    }
}

 

3. Cancel custom tasks

The Run method of the Task class provides an overloaded version that passes the CancellationToken parameter. The token is checked using the IsCancellationRequest attribute and the exception is triggered using the ThrowIfCancellationRequested method.

public async void CustomerTask()
{
    cts = new CancellationTokenSource();
    var list = new List<string>();
    list.Add("1");
    list.Add("2");
    list.Add("3");
    var deal_list = new List<int>();
    try
    {
        await Task.Run(() => {
            foreach (var item in list)
            {
                Random rd = new Random();
                int i = rd.Next(1, 100); //1 Number between 100,
                if (i % 2 == 0)
                {
                    OnCancel();//Cancel tasks when random numbers are even
                }
                if (cts.Token.IsCancellationRequested)
                {
                    Console.WriteLine("Handling task exceptions, rollback");
                    deal_list.Clear();
                    cts.Token.ThrowIfCancellationRequested();
                }
                deal_list.Add(Convert.ToInt32(item));
                Console.WriteLine(item);
            }
        }, cts.Token);
    }
    catch (OperationCanceledException ex)
    {

        Console.WriteLine(ex.Message);
    }
   
}

Posted by amyhughes on Wed, 10 Jul 2019 11:30:32 -0700