本文详细介绍了c#中async-await异步编程模型的使用方法,但并不涉及其原理。本文分为四个部分:异步方法的执行方式、异步方法的返回值、等待异步方法执行完毕和异步方法的简单异常处理。
1.异步方法的执行方式
使用async关键字声明的方法为异步方法,其中可包含await关键字,下面举例说明:
public async static Task AddEvent(Entry entry) { HttpClient sendLogClient = new HttpClient(); var values = new Dictionary<string, string> { { "token", "xxx" }, { "type", entry.type.ToString() }, { "subj", entry.subj }, { "obj", entry.obj}, { "time", entry.time.ToString("yyyy-MM-dd HH:mm:ss") }, }; var content = new FormUrlEncodedContent(values); try { HttpResponseMessage response = await sendLogClient.PostAsync("https://api.knightdusk.cn/set_entry.php", content); AddLog(response.ToString()); } catch(TaskCanceledException)//超时重试 { Task.Delay(10000).Wait(); AddEvent(entry).Wait(); } catch(Exception exp) { AddLog(exp.Message); } }
此函数实现的功能是使用HTTP POST方法异步地向api.knightdusk.cn下的一个php页面发送数据。函数首先根据传入参数进行数据的组织:将要POST的数据放入Dictionary类的键值对中,然后使用FormUrlEncodedContent函数形成HTTP POST所需要的数据(不是json,而是类似于表单的数据)。直到这里,程序都是同步执行的,与普通的函数尚无任何区别。接下来在try块类使用如下包含await函数的语句
HttpResponseMessage response = await sendLogClient.PostAsync("https://api.knightdusk.cn/set_entry.php", content);
注意到PostAsync和我们定义的方法一样是一个异步方法。await是“异步地等待方法执行”,在一个异步方法中以await方法调用另一个异步方法以后,外层异步方法将立即返回。举例来讲,如果我们在其他地方调用上述函数:
public void SomeFunction() { task = SomeClass.AddEvent(entry); Console.WriteLine("我也不知道我是来干嘛的"); task.Wait(); }
当AddEvent执行到await时,将立即返回。换句话说,sendLogClient.PostAsync以及await之后的其他语句将和Console.WriteLine在两个线程中同时执行。
2.异步方法的返回值
异步方法只可用三种返回类型:void,Task和Task<T>,下面举例说明其含义
//SomeClass.AddEvent()方法的返回类型是Task Task this_is_a_task = SomeClass.AddEvent(); //任何方法中可用 await SomeClass.AddEvent();//仅在async声明的异步方法中方可用 //sendLogClient.PostAsync()方法的返回类型是Task<HttpResponseMessage>; Task<HttpResponseMessage>; this_is_another_task = sendLogClient.PostAsync(); //任何方法中可用 HttpResponseMessage response = await sendLogClient.PostAsync(); //仅在async声明的异步方法中方可用 public void AFunction() { Task<int> task = AnotherFunction(); } public async Task<int> AnotherFunction() { //这里有一些语句,省略 HttpResponseMessage response = await sendLogClient.PostAsync(); //另外一些语句 return SomeInt; }
前面已经讲过,在AnotherFunction() 中使用await将导致AnotherFunction()所在的方法立刻返回,该返回值就是Task或者Task<T>泛型,其中包含了异步线程的信息。另一方面,await sendLogClient.PostAsync();直接返回方法的最终执行结果,即Task<T>中的类型T。这个值是在方法最后由return语句定义的。也就是说,await后面的方法将一次执行完毕,返回最终结果。
3.等待异步线程执行完毕
再考虑之前已经讲过的一个例子:
public void SomeFunction() { task = SomeClass.AddEvent(entry); Console.WriteLine("我也不知道我是来干嘛的"); task.Wait(); }
现考虑删除掉task.Wait();语句。这样一来,我们便不能保证在SomeFunction返回之前AddEvent可以执行完毕。假如SomeFunction换为主函数,则情况更为糟糕:输出文字之后程序结束关闭,此时访问链接的函数多半没有运行完毕,并且再也没有机会运行完毕了。因此,使用Wait方法等待异步线程执行完毕。
Task<HttpResponseMessage>; task = sendLogClient.PostAsync(); task.Wait(); HttpResponseMessage response = task.Result;
对于Task<T>类型的任务,使用Result属性获取最终运行结果。
注意:上文讲过,异步方法也可以使用void返回类型,但是这样将无法获得Task,也即无法跟踪任务的运行情况,因此一般不适用void返回类型。
提示:使用var关键字定义变量更为简洁,此处为了清晰期间没有这样做。
4.异步方法的异常处理
在考虑最初一个例子中的代码:
try { HttpResponseMessage response = await sendLogClient.PostAsync("https://api.knightdusk.cn/set_entry.php", content); AddLog(response.ToString()); } catch(TaskCanceledException)//超时重试 { Task.Delay(10000).Wait(); AddEvent(entry).Wait(); } catch(Exception exp) { AddLog(exp.Message); }
异步方法可以和普通方法使用类似的try-catch异常处理方法。但注意,c#异步方法超时将抛出TaskCanceledException,而不是我们可能想象的TimeOutException。此处超时重试的方法是递归调用自己,并使用Wait函数等待执行完毕。注意,此处没有设置最大重试次数,是不明智的选择。
Task.Delay(10000)的含义是,异步调用一个需要10000毫秒,即10秒才能完成的方法。立即对其使用了wait方法以实现延时10秒的效果。或者我们可以使用以下代码创建一个至少10秒才能完成的方法:
public void SomeFunction() { task = Task.Delay(10000); //此处省略代码XXX行 Console.WriteLine("我也不知道我为什么要让程序故意变慢"); task.Wait(); }
总结
最后,我们给异步等待模型的执行方法做一个总结:
public int AFunction() { Task<int> task = AnotherFunction(); SomeTimeSpendingFunction();//一个耗时的函数 Task.Wait(); int result = Task.Result; } public async Task<int> AnotherFunction() { //这里有一些语句,省略 HttpResponseMessage response = await sendLogClient.PostAsync(); //另外一些语句 return SomeInt; }
调用AFunction之后,执行过程如下:
- 调用AnotherFunction,执行“这里有一些语句”部分,直到await处。
- AnotherFunction返回Task<int>类型
- 在两个线程中sendLogClient.PostAsync()和SomeTimeSpendingFunction()同时执行。AnotherFuntion等待PostAsync执行完毕后才继续执行后面的代码。
- 假设SomeTimeSpendingFunction先执行完毕,此时主线程等待AnotherFunction继续执行完毕
- 主线程使用Result属性获得AnotherFunction的最终结果。