(10) Asynchronous - what is an asynchronous method (2)

1. What is an asynchronous method

An asynchronous method returns to the calling method before completing its work, and then completes its work while the calling method continues execution.

Features:

  • The method header contains the async method modifier.
  • Contains one or more await expressions, denoting tasks that can be completed asynchronously.
  • Must have one of 3 return types:
    • void
    • Task
    • Task<T>
    • ValueTask<T>
  • Any type that has a publicly accessible GetAwaiter method.
  • The formal parameters of an asynchronous method can be of any type and any number,But not for out or ref parameters.
  • By convention, the name of an asynchronous method should be suffixed with Async.
  • In addition to methods, lambda expressions and anonymous methods can also be asynchronous objects.
async Task<int> CountCharactersAsync(int id,string site)
{
Console.WriteLine("Starting CountCharacters");
WebClient wc = new WebClient();
//await 表达式
string result = await wc.DownloadStringTaskAsync( new Uri(site) );
//Lambda 表达式
// var result = await Task.Run(() => wc.DownloadString(new Uri(site)));
return result = Length;
}

async keyword

  • The method header of an asynchronous method must contain the async keyword and must precede the return type.
  • This modifier simply identifies that the method contains one or more await expressions.It does not create any asynchronous operations by itself.
  • The async keyword is a context keyword. In addition to being a method modifier (or lambda expression modifier, anonymous method modifier), async can also be used as an identifier. (like int async = 0;)

return type:

  • 1) Task: If the calling method does not need to return a value from the asynchronous method, but needs to check the status of the asynchronous method, then the asynchronous method can return an object of type Task. Async methods cannot return anything if they contain any return statements:
Task someTask = DoStuff.CalculateSumAsync(5,6);
...
someTask.Wait();
  • 2) ask<T>: If the calling method wants to obtain a value of type T from the call, the return type of the asynchronous method must be Task<T>. The calling method will get the entire value of type T by reading the Result property of the Task.
Task<T> value = DoStuff.CalcuateSumAsync(5,6);
...
Console.WriteLine($"Value:{ value.Result }");
  • 3)ValueTask<T>: This is a value type object, which is similar to Task<Tgt; but for cases where the task result may already be available. Because it is a value type,So it can be placed on the stack., without having to allocate space on the heap like Task<T> objects.
    class Program
    {
        static void Main(string[] args)
        {
            ValueTask<int> value = DoAsynStuff.CalculateSumAsync(0, 6);
            //处理其他事情
            Console.WriteLine($"Value:{ value.Result }");
            value = DoAsynStuff.CalculateSumAsync(5, 6);
            //处理其他事情
            Console.WriteLine($"Value:{ value.Result }");
            Console.ReadKey();
        }

        static class DoAsynStuff
        {
            public static async ValueTask<int> CalculateSumAsync(int i1,int i2)
            {
                // 如i1 == 0,则可以避免执行长时间运行的任务
                if(i1 == 0)
                {
                    return i2; 
                }

                int sum = await Task<int>.Run(() => GetSum(i1, i2));
                return sum;
            }

            private static int GetSum(int i1,int i2){  return i1 + i2; }
        }

    }

My programming environment does not support it ValueTask<T>. I checked the relevant information and found that it may only be supported by .NET 5.0 and above, and the highest version in my computer is only 4.6. Forget it, I won't test it, I will post the example first, and I will study it later when I have the conditions (I don't want to spend time installing the .NET framework).

  • 4) void: If the calling method just wants to execute the asynchronous method without any further interaction with it, the asynchronous method can return void type. Asynchronous methods cannot return anything if they contain any return statements. (This is the same as the return type Task. When await is included, the next code block can be executed before waiting for the task to complete. Otherwise, the next step can only be executed after waiting for the task to complete.)

Code example of the difference between void and Task:


   class MyDownloadString
    {
        const string Str = "https://www.baidu.com/";
        Stopwatch sw = new Stopwatch();

        public async void DoRun()
        {
            sw.Start();

            await returnTaskValue();
            Console.WriteLine("----------:{0,4:N0}", sw.Elapsed.TotalMilliseconds);
            returnVoidValue();
            Console.WriteLine("++++++++++:{0,4:N0}", sw.Elapsed.TotalMilliseconds);

            Thread.Sleep(500);
            Console.WriteLine();
            Console.WriteLine("暂停线程,为的是测试Task<string>,跟 Task 和 void 的无关:");

            Task<string> t = CountCharacters("Task<string>");
            Console.WriteLine("**********:{0,4:N0}", sw.Elapsed.TotalMilliseconds);
            //总结:
            //1、如果 Task 和 void 异步方法里有 await,则方法内需要等待完成才能返回
            //假设 Task 和 void 异步方法内有 await:
            //2、在同层级方法内调用异步方法,Task 方法 添加 await 等待任务完成才能执行下一个语句
            //void 方法并不会等待任务完成,而继续执行下一个语句

            //3、 Task<string> 在同一层方法被调用,则执行异步处理。跟 void 一样。

            //4、同一层方法作为调用者,如果被调用者为异步方法时:
            //执行 void 和 Task<string>方法,不需要 await,则执行异步处理。
            //执行 Task 方法 需要 await 执行等待任务完成,才能执行下一个语句;否则,就异步处理。

        }

        private async Task returnTaskValue()
        {
            Console.WriteLine("Task_Start:{0,4:N0}", sw.Elapsed.TotalMilliseconds);
            var result = await Task.Run(() => CountCharacters("Task"));
            Console.WriteLine("Task_End:{0,4:N0}", sw.Elapsed.TotalMilliseconds);
        }

        private async void returnVoidValue()
        {
            Console.WriteLine("Void_Start:{0,4:N0}", sw.Elapsed.TotalMilliseconds);
            var result = await Task.Run(() => CountCharacters("Void"));
            Console.WriteLine("Void_End:{0,4:N0}", sw.Elapsed.TotalMilliseconds);
        }

        private async Task<string> CountCharacters(string strId)
        {
            WebClient wc1 = new WebClient();
            Console.WriteLine("{0}_DownloadBefore:{1,4:N0}", strId, sw.Elapsed.TotalMilliseconds);
            var result = await wc1.DownloadStringTaskAsync(new Uri("https://www.baidu.com/"));
            Console.WriteLine("{0}_DownloadAfter:{1,4:N0}", strId, sw.Elapsed.TotalMilliseconds);
            return result;
        }
    }

Output result:

>Task_Start:   1
Task_DownloadBefore:  68
Task_DownloadAfter: 420
Task_End: 435
----------: 435
Void_Start: 436
Void_DownloadBefore: 437
++++++++++: 440
Void_DownloadAfter: 469
Void_End: 469

暂停线程,为的是测试Task<string>,跟 Task 和 void 的无关:
Task<string>_DownloadBefore: 950
**********: 952
Task<string>_DownloadAfter: 983

Async methods with a void return type use "call and forget":

 class Program
    {
        static void Main(string[] args)
        {
            DoAsynStuff.CalculateSumAsync(5, 6);
            //如果参数值为1,则先等待void 方法执行完成之后才能执行下一个语句
            //这样做法会有安全隐患,适合 void 异步方法处理时间短,Sleep 参数值设置足够大
            Thread.Sleep(200);
            Console.WriteLine("Program Exiting");
            Console.ReadKey();
        }

        static class DoAsynStuff
        {
            public static async void CalculateSumAsync(int i1,int i2)
            {
                int value = await Task.Run(() => GetSum(i1, i2));
                Console.WriteLine("Value:{0}", value);
            }

            private static int GetSum(int i1,int i2)
            {
                return i1 + i2;
            }
        }

    }
  • Any type that has an accessible GetAwaiter method.

2. Control flow of asynchronous methods

The structure of an asynchronous method consists of three distinct areas:

  • The part before the await expression
  • await expressions: tasks executed asynchronously
  • The part after the await expression

Figure - illustrates the control flow of an asynchronous method

Please add a picture description

There are currently two control flows: one within the async method and one within the calling method.
When the subsequent part encounters a return statement or reaches the end of the method:

  • If the method's return type is void, control flow exits.
  • If the method's return type is Task, the subsequent section sets the Task's state property and exits. If the return type is
    Task<T>or ValueTask<T>, the subsequent part will also set the object's Result property.

1. await expression

The await expression specifies a task to be executed asynchronously.

await task //task:空闲对象

An idle object is an instance of the awaitable type. The awaitable type refers to the type that contains the GetAwaiter method, which has no parameters and returns an object of awaitable type.

The awaiter type contains the following members:

bool IsCompleted{ get; }
void OnCompleted(Action);

void GetResult();
T GetResult();

2. About Task.Run:

Use the Task.Run method to create a Task.

//是以 Func<TReturn> 委托为参数
public static Task Run(Func<TReturn> function);
//还有其他重载的类型
...

There are three ways to create a delegate:

    class MyClass
    {
        public int Get10()
        {
            return 10;
        }

        public async Task DoWorkAsync()
        {
            Func<int> ten = new Func<int>(Get10);
            int a = await Task.Run(ten);
            int b = await Task.Run(new Func<int>(Get10));
            int c = await Task.Run(()=> { return 10; });

            Console.WriteLine($"{ a } { b } { c }");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Task t = (new MyClass()).DoWorkAsync();
            t.Wait();
            Console.ReadKey();
        }

    }

Output result:

10 10 10

Table - Return types and signatures of Task.Run overloads

return type sign
Task Run( Action action)
Task Run( Action action, CancellationToken token )
Task<TResult> Run( Func<TResult> function )
Task<TResult> Run( Func<TResult> function, CancellationToken token)
Task Run( Func<Task> function )
Task Run( Func<Task> function, CancellationToken token )
Task<TResult> Run( Func<Task<TResult>> function )
Task<TResult> Run( Func<Task<TResult>> function, CancellationToken token )

Table - Delegate types that can be used as the first parameter of the Task.Run method

commission type sign meaning
Action void Action() A method that takes no parameters and returns no value
Func<TResult> TResult Func() A method that does not require parameters but returns an object of type TRsult
Func<Task> Task Func() A method that takes no parameters but returns a simple Task object
Func<Task<TResult>> Task<TResult> Func() A method that takes no parameters but returns an object of type Task<T>

Example of 4 await statements:

static class MyClass
    {
        public static async Task DoWorkAsync()
        {
            //Run(Action)
            await Task.Run(() => Console.WriteLine(5.ToString()));
            //Run(TResult Func())
            Console.WriteLine((await Task.Run(() => 6)).ToString());
            //Run(Task Func())
            await Task.Run(() => Task.Run(() => Console.WriteLine(7.ToString())));
            //Run(Task<TResult> Func())
            int value = await Task.Run(() => Task.Run(() => 8));

            Console.WriteLine(value.ToString());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Task t = MyClass.DoWorkAsync();
            t.Wait();
            Console.WriteLine("Press Enter key to exit");
            Console.ReadKey();
        }
    }

Lambda function() => GetSum(5,6) satisfies the Func<TResult> delegate

int value = awaite Task.Run(() => GetSum(5,6));

3. Cancel an asynchronous operation

Some .NET asynchronous methods allow you to request termination of execution.
Two classes in the System.Threading.Tasks namespace are designed for this purpose: CancellationToken and CancellationTokenSource.

  • The CancellationToken object contains information about whether a task should be cancelled.
  • A task that owns a CancellationToken object needs to periodically check its token status. If the IsCancellationRequested property of the CancellationToken object is true, the task shall stop its operation and return.
  • CancellationToken is irreversible and can only be used once. That is, once the IsCancellationRequested property is set to true, it cannot be changed.
  • The CancellationTokenSource object creates CancellationToken objects that can be assigned to different tasks. Any object that holds a CancellationTokenSource for a task can call its Cancel method, which sets the CancellationTToken's IsCancellationRequested property to true.

Use these two classes to trigger the cancellation behavior:

 class Program
    {
        static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            CancellationToken token = cts.Token;

            MyClass mc = new MyClass();
            Task t = mc.RunAsync(token);

            //Thread.Sleep(3000);
            //cts.Cancel();

            t.Wait();
            Console.WriteLine($"Was Cancelled:{ token.IsCancellationRequested }");

            Console.ReadKey();
        }

        class MyClass
        {
            public async Task RunAsync(CancellationToken ct)
            {
                if (ct.IsCancellationRequested)
                    return;

                await Task.Run(() => CycleMethod(ct), ct);
            }

            void CycleMethod(CancellationToken ct)
            {
                Console.WriteLine("Starting CycleMethod");
                const int max = 5;
                for(int i = 0; i < max;i++)
                {
                    if (ct.IsCancellationRequested)
                        return;
                    Thread.Sleep(1000);
                    Console.WriteLine($"{ i + 1 } of { max } iterations completed");
                }
            }
        }
    }

Output result:

Starting CycleMethod
1 of 5 iterations completed
2 of 5 iterations completed
3 of 5 iterations completed
4 of 5 iterations completed
5 of 5 iterations completed
Was Cancelled:False

If you resume executing these two lines of code in the Main method:

Thread.Sleep(3000);//3秒后取消任务执行
cts.Cancel();

Output result:

Starting CycleMethod
1 of 5 iterations completed
2 of 5 iterations completed
3 of 5 iterations completed
Was Cancelled:True

Fourth, wait for the task synchronously in the calling method

Your code continues to perform other tasks, but at some point it may need to wait for a particular Task object to complete before continuing.

  static class MyDownloadString
    {
        public static void DoRun()
        {
            Task<int> t = CountCharactersAsync("http://illustratedcsharp.com");
            t.Wait();//等待任务 t 结束
            Console.WriteLine($"The task has finished,returning value { t.Result }.");
        }

        private static async Task<int> CountCharactersAsync(string site)
        {
            string result = await new WebClient().DownloadStringTaskAsync(new Uri(site));
            return result.Length;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyDownloadString.DoRun();
            Console.ReadKey();
        }
    }

Output result:

The task has finished,returning value 5164.

5. The Wait method is used for a single Task object.

1. Use WaitAll and WaitAny

For a group of Tasks, you can wait for all tasks to end, or wait for a certain task to end.

  • WaitAll
  • WaitAny
      class MyDownloadString
    {
        Stopwatch sw = new Stopwatch();

        public void DoRun()
        {
            sw.Start();

            Task<int> t1 = CountCharactersAsync(1,"http://illustratedcsharp.com");
            Task<int> t2 = CountCharactersAsync(2, "http://illustratedcsharp.com");

            //Task.WaitAll(t1, t2);
            //Task.WaitAny(t1, t2);

            Console.WriteLine("Task 1: {0} Finished",t1.IsCompleted?"":"Not");
            Console.WriteLine("Task 2: {0} Finished", t2.IsCompleted ? "" : "Not");
            Console.Read();
        }

        private  async Task<int> CountCharactersAsync(int id,string site)
        {
            WebClient wc = new WebClient();
            string result = await wc.DownloadStringTaskAsync(new Uri(site));
            Console.WriteLine("  Call {0} completed:    {1,4:N0} ms", id, sw.Elapsed.TotalMilliseconds);
            return result.Length;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyDownloadString ds = new MyDownloadString();
            ds.DoRun();
            Console.ReadKey();
        }
    }

Output result:

When the WaitAll and WaitAny methods are not used,t1 and t2 execute asynchronously

Task 1: Not Finished
Task 2: Not Finished
Call 1 completed: 598 ms
Call 2 completed: 600 ms

1) If in the DoRun method, resume Task.WaitAll(t1, t2); code:

Task.WaitAll(t1, t2);
//Task.WaitAny(t1, t2);

Output:
After using the WaitAll method, all tasks must wait for completion before the code can continue to execute.

Call 2 completed: 695 ms
Call 1 completed: 617 ms
Task 1: Finished
Task 2: Finished

2) If in the DoRun method, resume Task.WaitAny(t1, t2); code:

//Task.WaitAll(t1, t2);
Task.WaitAny(t1, t2);

Output result:

After using the WaitAll method, at least one Task needs to wait for completion before proceeding.

Call 2 completed: 659 ms
Call 1 completed: 658 ms
Task 1: Not Finished
Task 2: Finished

2. WaitAll and WaitAny also contain 4 overloads respectively.

sign describe
WaitAll
void WaitAll(params Task[] tasks) Wait for all tasks to complete
bool WaitAll(Task[] tasks, int millisecondsTimeout) Wait for all tasks to complete. If not all completed within the timeout period, return false and continue execution
void WaitAll(Task[] tasks, CancellationToken token) Wait for all tasks to complete, or wait for CancellationToken to signal cancellation
bool WaitAll(Task[] tasks, TimeSpan span) Wait for all tasks to complete. If not all completed within the timeout period, return false and continue execution
bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken token) Wait for all tasks to complete, or for a CancellationToken to signal cancellation. If none of the above happens within the timeout period, return false and continue execution
WaitAny
void WaitAny(params Task[] tasks) Wait for any task to complete
bool WaitAny(Task[] tasks, int millisecondsTimeout) Wait for any task to complete. If not completed within the timeout period, return false and continue execution
void WaitAny(Task[] tasks, CancellationToken token) Wait for any task to complete, or wait for CancellationToken to signal cancellation
bool WaitAny(Task[] tasks, TimeSpan span) Wait for any task to complete. If not completed within the timeout period, return false
bool WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken token) Wait for either task to complete, or for the CancellationToken to signal cancellation. If none of the above happens within the timeout period, return false and continue execution

3. Asynchronously wait for tasks in asynchronous methods

You can wait for one or all tasks to complete in an asynchronous method through the Task.WhenAll and Task.WhenAny methods. These two methods are called combinators.

     class MyDownloadString
    {
        Stopwatch sw = new Stopwatch();

        public void DoRun()
        {
            sw.Start();

            Task<int> t = CountCharactersAsync("http://www.baidu.com", "http://illustratedcsharp.com");

            Console.WriteLine("DoRun:   Task {0}Finshed", t.IsCompleted ?"":"Not");
            Console.WriteLine("DoRun:   Result = {0}", t.Result);
        }

        private  async Task<int> CountCharactersAsync(string site1,string site2)
        {
            WebClient wc1 = new WebClient();
            WebClient wc2 = new WebClient();
            Task<string> t1 = wc1.DownloadStringTaskAsync(new Uri(site1));
            Task<string> t2 = wc2.DownloadStringTaskAsync(new Uri(site2));

            List<Task<string>> tasks = new List<Task<string>>();
            tasks.Add(t1);
            tasks.Add(t2);

            await Task.WhenAll(tasks);//关键代码语句

            Console.WriteLine("  CCA:  T1 {0}Finished", t1.IsCompleted ? "" : "Not");
            Console.WriteLine("  CCA:  T2 {0}Finished", t2.IsCompleted ? "" : "Not");

            return t1.IsCompleted ? t1.Result.Length : t2.Result.Length;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyDownloadString ds = new MyDownloadString();
            ds.DoRun();
            Console.ReadKey();
        }
    }

Output:
In an asynchronous method, it is necessary to wait for all tasks to complete before the code can continue to execute.

DoRun: Task NotFinshed
CCA: T1 Finished
CCA: T2 Finished
DoRun: Result = 9269

If the Task.WhenAll method is changed to Task.WhenAny method:

Output:
Inside an asynchronous method, at least one task needs to wait to complete before code execution can continue.

DoRun: Task NotFinshed
CCA: T1 Finished
CCA: T2 NotFinished
DoRun: Result = 9269

6. Task.Delay method

Thread.Sleep will block the thread, but Task.Delay will not block the thread, and the thread can continue to process other work.

  class Simple
    {
        Stopwatch sw = new Stopwatch();

        public void DoRun()
        {
            Console.WriteLine("Caller:Before call");
            ShowDelayAsync();
            Console.WriteLine("Caller:After call");
            
        }

        private async void ShowDelayAsync()
        {
            sw.Start();
            Console.WriteLine($"  Before Delay:{ sw.ElapsedMilliseconds }");
            await Task.Delay(1000);
            Console.WriteLine($"   After Delay : { sw.ElapsedMilliseconds }");
        }
  
    }

    class Program
    {
        static void Main(string[] args)
        {
            Simple ds = new Simple();
            ds.DoRun();   
            Console.ReadKey();
        }
    }

Output result:

Caller:Before call
Before Delay:0
Caller:After call
After Delay : 1052

The Delay method contains 4 overloads that allow the time period to be specified in different ways, while also allowing the use of CancellationToken objects.

sign describe
Task Delay(int millisecondsDelay) After the delay time expressed in milliseconds expires, return the completed Task object
Task Delay(TimeSpan delay) Returns a completed Task object after the delay time represented by a .NET TimeSpan object has expired
Task Delay(int millisecondsDelay, CancellationToken token) After the delay time in milliseconds has expired, a Task object is returned that completes. The operation can be canceled by canceling the token
Task Delay(TimeSpan delay, CancellationToken token) Returns a completed Task object after the delay time represented by a .NET TimeSpan object has expired. The operation can be canceled by canceling the token

Guess you like

Origin blog.csdn.net/chen1083376511/article/details/131267879