若是须要 I/O 绑定(例如从网络请求数据或访问数据库),则须要利用异步编程。 还可使用 CPU 绑定代码(例如执行成本高昂的计算),对编写异步代码而言,这是一个不错的方案。web
异步编程的核心是 Task
和 Task<T>
对象,这两个对象对异步操做建模。 它们受关键字 async
和 await
的支持。 在大多数状况下模型十分简单:数据库
对于 I/O 绑定代码,当你 await
一个操做,它将返回 async
方法中的一个 Task
或 Task<T>
。编程
对于 CPU 绑定代码,当你 await
一个操做,它将在后台线程经过 Task.Run
方法启动。网络
编写代码前应考虑的两个问题:并发
1.你的代码是否会“等待”某些内容,例如数据库中的数据?app
若是答案为“是”,则你的工做是 I/O 绑定。异步
2.你的代码是否要执行开销巨大的计算?async
若是答案为“是”,则你的工做是 CPU 绑定。异步编程
若是你的工做为 I/O 绑定,请使用 async
和 await
(而不使用 Task.Run
)。 不该使用任务并行库。 相关缘由在深刻了解异步的文章中说明。spa
若是你的工做为 CPU 绑定,而且你重视响应能力,请使用 async
和 await
,并在另外一个线程上使用 Task.Run
生成工做。 若是该工做同时适用于并发和并行,则应考虑使用任务并行库。
你可能须要在按下按钮时从 Web 服务下载某些数据,但不但愿阻止 UI 线程。 只需执行以下操做便可轻松实现:
private readonly HttpClient _httpClient = new HttpClient(); downloadButton.Clicked += async (o, e) => { // This line will yield control to the UI as the request // from the web service is happening. // // The UI thread is now free to perform other work. var stringData = await _httpClient.GetStringAsync(URL); DoSomethingWithData(stringData); };
假设你正在编写一个移动游戏,在该游戏中,按下某个按钮将会对屏幕中的许多敌人形成伤害。 执行伤害计算的开销可能极大,并且在 UI 线程中执行计算有可能使游戏在计算执行过程当中暂停!
此问题的最佳解决方法是启动一个后台线程,它使用 Task.Run
执行工做,并 await
其结果。 这可确保在执行工做时 UI 能流畅运行。
private DamageResult CalculateDamageDone() { // Code omitted: // // Does an expensive calculation and returns // the result of that calculation. } calculateButton.Clicked += async (o, e) => { // This line will yield control to the UI while CalculateDamageDone() // performs its work. The UI thread is free to perform other work. var damageResult = await Task.Run(() => CalculateDamageDone()); DisplayDamage(damageResult); };
它无需手动管理后台线程,而是经过非阻止性的方式来实现。
此示例演示如何为一组 User
捕捉 userId
数据。
public async Task<User> GetUserAsync(int userId) { // Code omitted: // // Given a user Id {userId}, retrieves a User object corresponding // to the entry in the database with {userId} as its Id. } public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds) { var getUserTasks = new List<Task<User>>(); foreach (int userId in userIds) { getUserTasks.Add(GetUserAsync(userId)); } return await Task.WhenAll(getUserTasks); }
如下是使用 LINQ 进行更简洁编写的另外一种方法:
public async Task<User> GetUserAsync(int userId) { // Code omitted: // // Given a user Id {userId}, retrieves a User object corresponding // to the entry in the database with {userId} as its Id. } public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds) { var getUserTasks = userIds.Select(id => GetUserAsync(id)); return await Task.WhenAll(getUserTasks); }
尽管它的代码较少,但在混合 LINQ 和异步代码时须要谨慎操做。 由于 LINQ 使用延迟的执行,所以异步调用将不会像在 foreach()
循环中那样马上发生,除非强制所生成的序列经过对 .ToList()
或 .ToArray()
的调用循环访问。
LINQ 中的 Lambda 表达式使用延迟执行,这意味着代码可能在你并不但愿结束的时候中止执行。 若是编写不正确,将阻塞任务引入其中时可能很容易致使死锁。 此外,此类异步代码嵌套可能会对推断代码的执行带来更多困难。 Async 和 LINQ 的功能都十分强大,但在结合使用二者时应尽量当心。