async标记的方法返回类型为Task或void的区别

1,153 阅读3分钟

为建立中文知识库加块砖        ——中科大胡不归

问题描述

在C#中,我们在刚接触async+await时,发现使用async标记方法在返回类型为Task时,被ide提示如下:

Because this call is not awaited, execution of the current method continues 
before the call is completed. Consider applying the 'await' operator to the result of the call.

因为未等待此调用,所以在调用完成之前,当前方法将继续执行。

比如如下这种调用:

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            Test02();
           
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey(true);
        }

        private static async Task Test02()
        {
            await Task.Delay(10 * 1000);
            Console.WriteLine("Done!");
        }

编译检查你没有处理返回的Task,所以程序将不会等待Test02()的执行结果,由于Test02()异步执行,所以程序此时继续执行下一句打印。为什么会有这个警告呢,本文将尝试解释。

认识async+await

async+await是C#为我们异步编程接口,最简单的使用示例即:

        private async Task Test02()
        {
            await GetLocalIp();
            Console.WriteLine("Done!");
        }

await接耗时方法,即可实现异步执行。当然此时你也会收获如上的警告。为避免这个警告,你需要处理返回Task。

精彩示例

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            DoStuff();
            
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey(true);
        }
        
        private static void DoStuff()
        {
            var task = GetNameAsync();

            // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
            var anotherTask = task.ContinueWith(r => {
                Console.WriteLine("\n安排的明明白白:" + r.Result);
            });

            MainWorkOfApplicationIDontWantBlocked();

            // OR wait for the result AFTER
            var result = task.Result;
            Console.WriteLine("\n苦心等来的结果:" + result);
        }

        private static void MainWorkOfApplicationIDontWantBlocked()
        {
            const string tang = "\n门前大桥下,\n游过一群鸭,\n快来快来数一数,\n二四六七八";
            foreach (var cha in tang.ToCharArray())
            {
                Console.Write(cha);
                Thread.Sleep(500);
            }
        }

        private static async Task<string> GetNameAsync()
        {
            string firstname = await PromptForStringAsync("Enter your first name: ");
            string lastname = await PromptForStringAsync("Enter your last name: ");
            return firstname + lastname;
        }
        
        // contrived example (edited in response to Servy's comment)
        private static Task<string> PromptForStringAsync(string prompt)
        {
            return Task.Factory.StartNew(() => {
                Console.Write(prompt);
                return Console.ReadLine();
            });
        }

MainWorkOfApplicationIDontWantBlocked是指不想被阻塞的主任务,这里是演唱儿歌。

GetNameAsync是同时允许用户输入姓名的异步程序。

var result = task.Result;中result是GetNameAsync返回类型string,当然task.Result也会阻塞进程。

效果演示

此示例实现主线程演唱儿歌不中断,同时用户能完成输入的异步任务。

效果演示

返回Task与返回void

  • Task<T> 一个返回为Task<T>的异步方法的结果是可以被等待的,并在任务完成时,它会返回一个类型T的结果。

  • Task 一个返回为Task的异步方法的结果是可以被等待的,当任务完成时,任务的完成是可以被调度的。

  • void 一个返回为void的异步方法不能等待,这是一种“fire and forget(一x没)”的方法。它是异步工作的,我们无法知道何时完成。事件触发,处理程序执行,不会去“等待”事件处理程序返回的任务,因为事件处理程序也不会返回任务,即使它们返回了。什么地方可以使用这种方法呢?通常是不需要用户控制的地方。

参考文章

  1. warning this call is not awaited, execution of the current method continues
  2. What's the difference between returning void and returning a Task?