在ASP.NET4.5中使用异步方法的魔力外加一个疑难杂症
[原文发表地址] The Magic of using Asynchronous Methods in ASP.NET 4.5 plus an important gotcha
[原文发表时间] 2013-08-29
首先,我鼓励你去听听播客Hanselminutes的第327集,那一集的名字是“Everything .NET programmers know about Asynchronous Programming is wrong”我从这一集学到了很多。我保证你也会受益匪浅。
通常我们会发现我们会在一个页面里面做三四件事情,从一些地方下载东西。也许是从硬盘下载,调用web服务和数据库。
典型的做法是, 你可以顺序处理这些事情,同步地,累加每一个任务的总持续时间:
1: public void Page_Load(object sender, EventArgs e)
2: {
3: var clientcontacts = Client.DownloadString("api/contacts");
4: var clienttemperature = Client.DownloadString("api/temperature");
5: var clientlocation = Client.DownloadString("api/location");
6:
7:
8: var contacts = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Contact>>(clientcontacts);
9: var location = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(clientlocation);
10: var temperature = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(clienttemperature);
11:
12: listcontacts.DataSource = contacts;
13: listcontacts.DataBind();
14: Temparature.Text = temperature;
15: Location.Text = location;
16: }
这里的三个调用各需花一秒钟,所以总的是三秒钟,他们是一个一个发生的。
凭直觉你可能想要通过用async标注public void Page_Load 使这些任务以异步的方式运行,然后等待这三个任务。
但是 Page_Load是一个页生命周期事件,并且是一个返回值为空的事件处理程序。ASP.NET团队的Damian Edwards说:
在web表单中,只有特定的一些事件支持没有返回值的异步事件处理,正如你所知道的,它真的只是用于一些简单的任务。对于任何
具有实际复杂性的异步工作我们建议使用PageAsyncTask
Levi 也是来自ASP.NET团队则使用更为强烈的措辞。基本上是,请不要在没有返回值的事件处理程序上使用异步,这不值得。
在web 应用中异步事件本质上是个奇怪的野兽。返回类型为空的异步事件是针对发后即忘的编程模型的。这适用于Windows UI应用,因为Windows
UI应用会一直停留在那里直到操作系统将它杀死,所以无论什么时候异步回调运行都确保会有一个UI线程与它交互。在Web应用中,由于请求仅存于
被定义瞬间,这个模型就不适用了。假如这个异步回调发生在请求结束后,就不能保证回调交互所需的数据结构还在一个很好的状态。这就是为什么
说在web应用中用发后即忘(还有空返回的异步)根本就是一个坏主意。
也就是说,我们做非常疯狂的操作去实现Page_Load work这样很简单的工作,但是支持这个的代码却是极其复杂并且对于任何超出基本的场景也没有经过很好的测试。所以假如你需要可靠性我推荐你坚持使用RegisterAsyncTask。
使用异步绑定在没有返回值的事件上既不稳定也不可靠,然而,你只需要调用Page.RegisterAyncTask - 这不会造成任何问题而且你还会拥有一个更为灵活的空间里。
1: public void Page_Load(object sender, EventArgs e)
2: {
3: RegisterAsyncTask(new PageAsyncTask(LoadSomeData));
4: }
5:
6: public async Task LoadSomeData()
7: {
8:
9: var clientcontacts = Client.DownloadStringTaskAsync("api/contacts");
10: var clienttemperature = Client.DownloadStringTaskAsync("api/temperature");
11: var clientlocation = Client.DownloadStringTaskAsync("api/location");
12:
13: await Task.WhenAll(clientcontacts, clienttemperature, clientlocation);
14:
15: var contacts = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Contact>>(await clientcontacts);
16: var location = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clientlocation);
17: var temperature = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clienttemperature);
18:
19: listcontacts.DataSource = contacts;
20: listcontacts.DataBind();
21: Temparature.Text = temperature;
22: Location.Text = location;
23: }
你可以通过移除 Task更简化这个事件(在这种情况下,是完全没有必要的).WhenAll接着只需等待所有任务的结果一个个出来。这里,当Task.WhenAll发生的时候,这些任务已经回来了。结果是一样的。这既具有了类似同步代码的可读性优点,又有了异步的好处。
1: public async Task LoadSomeData()
2: {
3:
4: var clientcontacts = Client.DownloadStringTaskAsync("api/contacts");
5: var clienttemperature = Client.DownloadStringTaskAsync("api/temperature");
6: var clientlocation = Client.DownloadStringTaskAsync("api/location");
7:
8: var contacts = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Contact>>(await clientcontacts);
9: var location = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clientlocation);
10: var temperature = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(await clienttemperature);
11:
12: listcontacts.DataSource = contacts;
13: listcontacts.DataBind();
14: Temparature.Text = temperature;
15: Location.Text = location;
16: }
这个现在只需花费一点点时间,因为这三个异步任务同时发生。像这样的异步是最有用的(最明显的)当你有多个任务并且其中的任意一个不依赖于另外一个。
一定记得像这样标注.aspx page中的Async="true"
1: <%@ Page Title="Async" Language="C#" CodeBehind="Async.aspx.cs" Inherits="Whatever" Async="true" %>
2:
相关链接
-
[在ASP.NET4.5中的异步事件](https://www.asp.net/web-forms/tutorials/aspnet-45/using-asynchronous-methods-in-aspnet-45)