C# 异步编程

若是须要 I/O 绑定(例如从网络请求数据或访问数据库),则须要利用异步编程。 还可使用 CPU 绑定代码(例如执行成本高昂的计算),对编写异步代码而言,这是一个不错的方案。web

异步模型的基本概述

异步编程的核心是 Task 和 Task<T> 对象,这两个对象对异步操做建模。 它们受关键字 async 和 await 的支持。 在大多数状况下模型十分简单:数据库

对于 I/O 绑定代码,当你 await 一个操做,它将返回 async 方法中的一个 Task 或 Task<T>编程

对于 CPU 绑定代码,当你 await 一个操做,它将在后台线程经过 Task.Run 方法启动。网络

 

识别 CPU 绑定和 I/O 绑定工做

编写代码前应考虑的两个问题:并发

1.你的代码是否会“等待”某些内容,例如数据库中的数据?app

若是答案为“是”,则你的工做是 I/O 绑定。异步

2.你的代码是否要执行开销巨大的计算?async

若是答案为“是”,则你的工做是 CPU 绑定。异步编程

若是你的工做为 I/O 绑定,请使用 async 和 await(而不使用 Task.Run)。 不该使用任务并行库。 相关缘由在深刻了解异步的文章中说明。spa

若是你的工做为 CPU 绑定,而且你重视响应能力,请使用 async 和 await,并在另外一个线程上使用 Task.Run 生成工做。 若是该工做同时适用于并发和并行,则应考虑使用任务并行库

 

I/O 绑定示例:从 Web 服务下载数据

你可能须要在按下按钮时从 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);
};

CPU 绑定示例:为游戏执行计算

假设你正在编写一个移动游戏,在该游戏中,按下某个按钮将会对屏幕中的许多敌人形成伤害。 执行伤害计算的开销可能极大,并且在 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 的功能都十分强大,但在结合使用二者时应尽量当心。

相关文章
相关标签/搜索