C# 异步编程

发布于:2025-03-17 ⋅ 阅读:(15) ⋅ 点赞:(0)

概述

同步:指必须等待前一个操作完成,后续操作才能继续。同步操作会阻塞线程直到任务完成。

异步:异步操作不会阻塞线程,允许程序在等待某个任务完成的同时,继续执行其他任务。

异步编程适用场景

1、从网络请求数据

2、访问数据库

3、读取和写入到文件系统

4、执行成本高昂的计算

好处

通过使用异步编程,可以避免性能瓶颈并增强应用程序的总体响应能力,不会阻塞线程,增强用户体验。

规则

1、async 关键字用于标记一个方法为异步方法

2、按照约定,异步方法的名称以“Async”后缀结尾。例如CalculateAsync

3、async 方法需要在主体中有 await 关键字,否则它们将永不暂停

4、异步方法必须返回一个 Task 或 Task<T>,表示该方法的异步执行结果。

代码模板

async 和 await异步流程

1、使用await指定暂停点,只有等待暂停点之前的异步任务完成后才能通过该点,并执行await之后的逻辑。

2、方法体中可以包含一个或多个await运算符。

无参无返回

public async Task GetUrlContentAsync()
{
    using var client = new HttpClient();
    Task<string> getStringTask = client.GetStringAsync("https://learn.microsoft.com/dotnet");
    DoIndependentWork();
    string contents = await getStringTask;
    Console.WriteLine(contents);    
}

有参无返回

public async Task GetUrlContentAsync(string url)
{
    using var client = new HttpClient();
    Task<string> getStringTask = client.GetStringAsync(url);
    DoIndependentWork();
    string contents = await getStringTask;
    Console.WriteLine(contents);    
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}

有参有返回

public async Task<int> GetUrlContentLengthAsync(string url)
{
    using var client = new HttpClient();
    Task<string> getStringTask = client.GetStringAsync(url);
    DoIndependentWork();
    string contents = await getStringTask;
    return contents.Length;
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}
Console.WriteLine("异步任务开始");

// 调用异步方法
var url = "https://learn.microsoft.com/dotnet";
var result = await test.GetUrlContentLengthAsync(url);

Console.WriteLine("异步任务完成  "+ result);

应用场景

1、游戏中,执行复杂的逻辑运算

如果在游戏中进行某个开销极大的逻辑运算,那么可能会造成游戏运行卡顿或者UI界面无法操作。 此问题的最佳解决方法是启动一个后台线程,使用 Task.Run 执行工作,并使用 await 等待其结果。 这可确保在执行工作时 UI 能流畅运行。

 public async Task CalculateAsync()
 {
     await Task.Run(() =>
     {
         Console.WriteLine("计算中");
            
     });
     Console.WriteLine("计算好了");
 }
Console.WriteLine("异步任务开始");

// 调用异步方法
await CalculateAsync();

Console.WriteLine("异步任务完成");

2、文件异步读写

写入文本

public async Task SimpleWriteAsync()
{
    string filePath = "simple.txt";
    string text = $"Hello World";

    await File.WriteAllTextAsync(filePath, text);
}

读取文本

public async Task SimpleReadAsync()
{
    string filePath = "simple.txt";
    string text = await File.ReadAllTextAsync(filePath);

    Console.WriteLine(text);
}

3、从网络提取数据

 static readonly HttpClient client = new HttpClient();
 public async Task SimpleWriteAsync(string url)
 {
     // 在try/catch块中调用异步网络方法来处理异常
     try
     {
         //向指定的Uri发送GET请求,并以字符串形式返回响应体,异步操作
         string responseBody = await client.GetStringAsync(url);
         Console.WriteLine(responseBody);
     }
     catch (HttpRequestException e)
     {
         Console.WriteLine("Message :{0} ", e.Message);
     }
 }
Console.WriteLine("异步任务开始");

var url = "http://www.contoso.com/";
// 调用异步方法
await SimpleWriteAsync(url);

Console.WriteLine("异步任务完成");

 4、等待多个任务完成

如果需要并行检索多个数据部分的情况。 Task API 包含两种方法(即Task.WhenAll和Task.WhenAny),这些方法允许你编写在多个后台作业中执行非阻止等待的异步代码。

static readonly HttpClient client = new HttpClient();
async Task GetWebMsgAsync(string url)
{
    // 在try/catch块中调用异步网络方法来处理异常
    try
    {
        var response = await client.GetStringAsync(url);
        Console.WriteLine(response);
        Console.WriteLine("================================================");
    }
    catch (HttpRequestException e)
    {
        Console.WriteLine("Message :{0} ", e.Message);
    }
}

public async Task GetWebDataList()
{
    var taskList = new List<Task>();
    var list = new List<string>()
    {
        "https://weibo.com/",
        "https://www.bilibili.com/",
        "https://blog.csdn.net/",
    };

    foreach (var url in list)
    {

        taskList.Add(GetWebMsgAsync(url));
    }

    await Task.WhenAll(taskList);
}
Console.WriteLine("异步任务开始");

// 调用异步方法
await GetWebDataList();

Console.WriteLine("异步任务完成");

注意

1、 I/O 绑定(例如从网络请求数据、访问数据库或读取和写入到文件系统)

 CPU 绑定(例如执行成本高昂的计算)

如果你的工作为 I/O 绑定,请使用 async 和 await(而不使用 Task.Run)。 不应使用任务并行库。如果你的工作属于 CPU 绑定,并且重视响应能力,请使用 async 和 await,但在另一个线程上使用 Task.Run 。

2、在 LINQ 表达式中使用异步 lambda 时请谨慎LINQ 中的 Lambda 表达式使用延迟执行,这意味着代码可能在你并不希望结束的时候停止执行。 如果编写不正确,将阻塞任务引入其中时可能很容易导致死锁。 此外,此类异步代码嵌套可能会对推断代码的执行带来更多困难。 Async 和 LINQ 的功能都十分强大,但在结合使用两者时应尽可能小心。

参考资料

异步编程场景 - C# | Microsoft Learn