开放的编程资料库

当前位置:我爱分享网 > C#教程 > 正文

C# 异步/等待

C#async/await教程展示了如何在C#中使用async和await关键字。

通过异步编程,我们可以在主程序执行的同时执行任务。asyncawait关键字简化了C#中的异步编程。C#在语言中内置了异步编程模型。

并发编程用于两种任务:I/O密集型任务和CPU密集型任务。从网络请求数据、访问数据库或读取和写入都是IO绑定任务。CPU-boud任务是计算量大的任务,例如数学计算或图形处理。

async修饰符用于方法、lambda表达式或匿名方法以创建异步方法。async方法同步运行,直到它到达它的第一个await运算符,此时该方法在等待的任务完成时被挂起。与此同时,控制权返回给方法的调用者。

await运算符暂停对封闭的async方法的计算,直到异步操作完成。当异步操作完成时,await运算符返回操作结果(如果有)。

如果async方法不包含await运算符,则该方法将同步执行。

在C#中,Task表示并发操作。

C#简单同步例子

在下一个例子中,我们同步执行三个方法。

using System.Diagnostics;

var sw = new Stopwatch();
sw.Start();

f1();
f2();
f3();

sw.Stop();

var elapsed = sw.ElapsedMilliseconds;
Console.WriteLine($"elapsed: {elapsed} ms");

void f1() 
{
    Console.WriteLine("f1 called");
    Thread.Sleep(4000);
}

void f2() 
{
    Console.WriteLine("f2 called");
    Thread.Sleep(7000);
}

void f3() 
{
    Console.WriteLine("f3 called");
    Thread.Sleep(2000);
}

使用Thread.Sleep,我们模拟了一些更长的计算。

var sw = new Stopwatch();
sw.Start();

我们用Stopwatch测量方法的执行时间。

f1();
f2();
f3();

方法是连续调用的。

$ dotnet run
f1 called
f2 called
f3 called
elapsed: 13034 ms

在我们的系统上,执行这三个函数需要13秒。

C#简单异步例子

现在,该示例使用async/await关键字重写。

using System.Diagnostics;


var sw = new Stopwatch();
sw.Start();

Task.WaitAll(f1(), f2(), f3());

sw.Stop();

var elapsed = sw.ElapsedMilliseconds;
Console.WriteLine($"elapsed: {elapsed} ms");

async Task f1()
{
    await Task.Delay(4000);
    Console.WriteLine("f1 finished");
}

async Task f2()
{
    await Task.Delay(7000);
    Console.WriteLine("f2 finished");
}

async Task f3()
{
    await Task.Delay(2000);
    Console.WriteLine("f3 finished");
}

我们测量了三种异步方法的执行时间。

Task.WaitAll(f1(), f2(), f3());

Task.WaitAll等待所有提供的任务完成执行。

async Task f1()
{
    await Task.Delay(4000);
    Console.WriteLine("f1 finished");
}

f1方法使用async修饰符并返回一个Task。在方法体内,我们对Task.Delay使用了await运算符。

$ dotnet run
f3 finished
f1 finished
f2 finished
elapsed: 7006 ms

现在执行需要7秒。另请注意,任务完成的顺序不同。

C#异步主方法

当我们在Main方法中使用await运算符时,我们必须用async修饰符标记它。

using System.Diagnostics;


namespace AsyncMain
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var sw = new Stopwatch();
            sw.Start();

            Console.WriteLine("task 1");
            Task task1 = doWork();

            Console.WriteLine("task 2");
            Task task2 = doWork();

            Console.WriteLine("task 3");
            Task task3 = doWork();

            await Task.WhenAll(task1, task2, task3);

            Console.WriteLine("Tasks finished");

            sw.Stop();

            var elapsed = sw.ElapsedMilliseconds;
            Console.WriteLine($"elapsed: {elapsed} ms");
        }

        static async Task doWork()
        {
            await Task.Delay(1500);
        }
    }
}

Main方法中,我们调用了三次doWork

$ dotnet run
task 1
task 2
task 3
Tasks finished
elapsed: 1550 ms

C#异步读取文件

C#有许多内置的异步读取文件的方法。例如,File.ReadAllTextAsync异步打开一个文本文件,读取文件中的所有文本,然后关闭文件。

using System.Diagnostics;


var task1 = File.ReadAllTextAsync("data1.txt");
var task2 = File.ReadAllTextAsync("data2.txt");
var task3 = File.ReadAllTextAsync("data3.txt");
var task4 = File.ReadAllTextAsync("data4.txt");

Console.WriteLine("doing some work");

var tasks = new Task[] { task1, task2, task3, task4 };

Task.WaitAll(tasks);

var content1 = await task1;
var content2 = await task2;
var content3 = await task3;
var content4 = await task4;

Console.WriteLine(content1.TrimEnd());
Console.WriteLine(content2.TrimEnd());
Console.WriteLine(content3.TrimEnd());
Console.WriteLine(content4.TrimEnd());

在示例中,我们异步读取四个文件。

var content1 = await task1;

使用await,我们异步解包任务的结果。

C#CPU绑定异步任务

在下一个示例中,我们将处理CPU密集型计算。

var tasks = new List<Task<int>>();

tasks.Add(Task.Run(() => DoWork1()));
tasks.Add(Task.Run(() => DoWork2()));

await Task.WhenAll(tasks);

Console.WriteLine(await tasks[0]);
Console.WriteLine(await tasks[1]);

async Task<int> DoWork1()
{
    var text = string.Empty;

    for (int i = 0; i < 100_000; i++)
    {
        text += "abc";
    }

    Console.WriteLine("concatenation finished");

    return await Task.FromResult(text.Length);
}

async Task<int> DoWork2()
{
    var text = string.Empty;

    for (int i = 0; i < 100_000; i++)
    {
        text = $"{text}abc";
    }

    Console.WriteLine("interpolation finished");

    return await Task.FromResult(text.Length);
}

我们有两种计算密集型方法,可以连接字符串十万次。

tasks.Add(Task.Run(() => DoWork1()));
tasks.Add(Task.Run(() => DoWork2()));

Task.Run方法将指定的工作排入队列以在ThreadPool上运行,并返回该工作的任务或Task句柄。

return await Task.FromResult(text.Length);

我们返回与text.Length连接的字符数。Task.FromResult创建一个Task,该任务已成功完成并获得指定结果。

C#多个异步请求

HttpClient类用于发送HTTP请求和接收来自指定资源的HTTP响应。

using System.Text.RegularExpressions;

var urls = new string[] { "http://webcode.me", "http://example.com",
    "http://httpbin.org", "https://ifconfig.me", "http://termbin.com",
    "https://github.com"
};

var rx = new Regex(@"<title>\s*(.+?)\s*</title>",
  RegexOptions.Compiled);

using var client = new HttpClient();

var tasks = new List<Task<string>>();

foreach (var url in urls)
{
    tasks.Add(client.GetStringAsync(url));
}

Task.WaitAll(tasks.ToArray());

var data = new List<string>();

foreach (var task in tasks)
{
    data.Add(await task);
}

foreach (var content in data)
{
    var matches = rx.Matches(content);

    foreach (var match in matches)
    {
        Console.WriteLine(match);
    }
}

我们异步下载给定的网页并打印它们的HTML标题标签。

tasks.Add(client.GetStringAsync(url));

GetStringAsync向指定的url发送GET请求,并在异步操作中将响应正文作为字符串返回。它返回一个新任务。

Task.WaitAll(tasks.ToArray());

Task.WaitAll等待所有提供的任务完成执行。

data.Add(await task);

await解包操作的结果。

$ dotnet run
<title>My html page</title>
<title>Example Domain</title>
<title>httpbin.org</title>
<title>termbin.com - terminal pastebin</title>
<title>GitHub: Where the world builds software · GitHub</title>

在本文中,我们使用async/await关键字在C#中创建异步程序。

列出所有C#教程。

未经允许不得转载:我爱分享网 » C# 异步/等待

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏