C#异步编程

发布于:2025-09-02 ⋅ 阅读:(18) ⋅ 点赞:(0)


前言

异步编程


一、异步方法1

异步方法:用async关键字修饰的方法
1、异步方法的返回值一般是Task,T是真正的返回值类型,Task。惯例:异步方法名字以Async结尾。
2、即使方法没有返回值,也最好把返回值声明为非泛型的Task.

// 返回值声明为void不建议
public async void GetFileCharLenght(string filePath)
{
     string s = await File.ReadAllTextAsync(filePath);
}
// 返回值声明为非泛型的Task 建议
public async Task GetFileCharLenght(string filePath)
{
     string s = await File.ReadAllTextAsync(filePath);
}

3、调用泛型方法时,一般在方法前加上await关键字,这样拿到的返回值就是泛型指定的T类型

// 拿到的返回值就是泛型指定的string类型
public async Task<string> GetFileCharLenght(string filePath)
{
     string s = await File.ReadAllTextAsync(filePath);//方法前加上await关键字
    return s;
}

4、异步方法的传染性:一个方法种如果有await调用,则这个方法就必须修饰为async

// 一个方法种如果有await调用,则这个方法就必须修饰为async
public async Task Main()
{
    await GetFileCharLenght(@"C:\a.txt");
}

public async Task<string> GetFileCharLenght(string filePath)
{
     string s = await File.ReadAllTextAsync(filePath);
    return s;
}

二、异步方法2

如果同样的功能,既有同步方法,又有异步方法,那么首先使用异步方法。,对于不支持的异步方法,可以使用Wait()(无返回值);Result()(有返回值);风险锁死,尽量不要使用。

// 假如MainWindow不支持异步操作,也就是不能用async关键字修改,
// 而File.ReadAllTextAsync(@"C:\a.txt")这个异步方法有返回值,
// 可以使用Result关键字,将异步方法的返回值取出
public MainWindow()
{
    InitializeComponent();
    string s =  File.ReadAllTextAsync(@"C:\a.txt").Result;
}
// 假如Main Window不支持异步操作,而File.ReadAllTextAsync(@"C:\a.txt")这个异步方法有返回值,可以使用Result关键字
public MainWindow()
{
    InitializeComponent();

    string s =  File.ReadAllTextAsync(@"C:\a.txt").Result;
    // 如果没有返回值可以使用Wait等待异步方法执行完毕
    File.WriteAllTextAsync(@"C:\a.txt", "11111111").Wait();
}

lamda表达式中使用异步方法

// 异步lamda表达式,在lamda表达式中使用async 关键字修饰即可
ThreadPool.QueueUserWorkItem(async (obj) =>
{
       await File.WriteAllTextAsync(@"C:\a.txt", "11111111");
 });

二、异步方法3

异步方法不等于多线程,异步方法的代码不会自动在新线程中执行,除非把代码放到新线程中执行。

// 执行前后线程ID不变
static async Task Main()
{
    // To customize application configuration such as set high DPI settings or default font,
    // see https://aka.ms/applicationconfiguration.
    ApplicationConfiguration.Initialize();
    Application.Run(new Form1());

    Debug.WriteLine("之前"+Thread.CurrentThread.ManagedThreadId);
    double r = await CalcAsync(5000);
    Debug.WriteLine($"r={r}");
    Debug.WriteLine("之后" + Thread.CurrentThread.ManagedThreadId);
}
public static async Task<double> CalcAsync(int n)
{
    Debug.WriteLine("CalcAsync"+Thread.CurrentThread.ManagedThreadId);
    double result = 0;
    Random rnd = new Random();
    for (int i = 0; i < n; i++) 
    {
        result += rnd.NextDouble();
    }
    return result;
}
// 结果
/*
之前1
CalcAsync1
r=2495.1055014091776
之后1
*/

除非把代码放到新线程中执行。必须手动将代码放入线程中,也就是使用Task.Run()方法,会自动根据返回值推断出Task.Run()泛型的类型

public static async Task<double> CalcAsync(int n)
{
    return await Task.Run(() =>
    {
        Debug.WriteLine("CalcAsync" + Thread.CurrentThread.ManagedThreadId);
        double result = 0;
        Random rnd = new Random();
        for (int i = 0; i < n; i++)
        {
            result += rnd.NextDouble();
        }
        return result;
    });
}
// 运行结果
/*
之前1
CalcAsync 5
r=2498.2461521182872
之后5
前后线程id不一样,异步方法执行完后被放到新的线程中执行了
*/

二、异步方法4

如果想要在异步方法中暂停一段时间,不要用Thread.Sleep(),因为它会阻塞调用线程,而要用await Task.Delay()。
例如,在winfrom程序中,如果使用Thread.Sleep()就会阻塞UI线程在睡眠这段时间是无法操作窗体.

private async void button1_Click(object sender, EventArgs e)
{
    using (HttpClient client = new HttpClient()) 
    {
        string s1 = await client.GetStringAsync("https://www.youzack.com");
        textBox1.Text = s1.Substring(0,20);
        Thread.Sleep(5000);
        string s2 = await client.GetStringAsync("https://www.baidu.com");
        textBox1.Text = s2.Substring(0,20);
    }
}

效果
在这里插入图片描述
使用 await Task.Delay()

private async void button1_Click(object sender, EventArgs e)
{
    using (HttpClient client = new HttpClient()) 
    {
        string s1 = await client.GetStringAsync("https://www.youzack.com");
        textBox1.Text = s1.Substring(0,20);
        await Task.Delay(5000);
        string s2 = await client.GetStringAsync("https://www.baidu.com");
        textBox1.Text = s2.Substring(0,20);
    }
}

效果
请添加图片描述

二、异步方法5

CancellationToken
有时候需要提前终止任务,比如,请求超时、用户取消请求。很多异步方法都有CancellationToken参数,用于获得提前终止执行的信号。

CancellationToken是一个结构体
有个 None:空的成员
bool IsCancellationRequested 是否发出取消任务的请求
ThrowIfCancellationRequested() 如果任务被取消,执行到这句话就抛异常。

一般不直接使用CancellationToken来获取CancellationToken对象,而是使用CancellationTokenSource来获取CancellationToken对象。CancellationTokenSource中的方法,CancelAfter() 超时后发出取消信号,Cancel()发出取消信号,

private async void button1_Click(object sender, EventArgs e)
{
    Download1Async("https://www.baidu.com", 100);
}
/// <summary>
/// 未使用CancellationToken,程序会一直执行直到访问改网站100次
/// </summary>
/// <param name="url"></param>
/// <param name="n"></param>
/// <returns></returns>
public async Task Download1Async(string url,int n)
{
    using (HttpClient client = new HttpClient())
    {
        for(var i=0; i < n; i++)
        {
            string s1 = await client.GetStringAsync(url);
            textBox1.Text = textBox1.Text + s1.Substring(0, 20);
        }
       
    }
}

使用CancelAfter来终止请求

private async void button1_Click(object sender, EventArgs e)
{
    CancellationTokenSource source = new CancellationTokenSource();
    source.CancelAfter(5000);// 5秒之后终止如果请求还未结束,提前终止请求
    CancellationToken token = source.Token;
    Download2Async("https://www.baidu.com", 100 , token);
}
public async Task Download2Async(string url, int n,CancellationToken cancellationToken)
{
    using (HttpClient client = new HttpClient())
    {
        for (var i = 0; i < n; i++)
        {
            string s1 = await client.GetStringAsync(url);
            textBox1.Text = textBox1.Text + s1.Substring(0, 20);
            if (cancellationToken.IsCancellationRequested) 
            {
                textBox1.Clear();
                textBox1.Text = "取消请求";
                break;
            }
        }
    }
}

效果 5秒内请求未执行完,请求被提前终止。
请添加图片描述
使用ThrowIfCancellationRequested()来终止请求

private async void button1_Click(object sender, EventArgs e)
{
    CancellationTokenSource source = new CancellationTokenSource();
    source.CancelAfter(5000);// 5秒之后终止如果请求还未结束,提前终止请求
    CancellationToken token = source.Token;
    Download2Async("https://www.baidu.com", 100 , token);
}
public async Task Download2Async(string url, int n,CancellationToken cancellationToken)
{
    using (HttpClient client = new HttpClient())
    {
        for (var i = 0; i < n; i++)
        {
            string s1 = await client.GetStringAsync(url);
            textBox1.Text = textBox1.Text + s1.Substring(0, 20);
            //if (cancellationToken.IsCancellationRequested) 
            //{
            //    textBox1.Clear();
            //    textBox1.Text = "取消请求";
            //    break;
            //}
            cancellationToken.ThrowIfCancellationRequested();
        }

    }
}

抛出异常引发的异常:“System.OperationCanceledException”(位于 System.Private.CoreLib.dll 中)

使用GetStringAsync,时传入cancellationToken,让GetStringAsync来处理。

private async void button1_Click(object sender, EventArgs e)
{
    CancellationTokenSource source = new CancellationTokenSource();
    source.CancelAfter(5000);// 5秒之后终止如果请求还未结束,提前终止请求
    CancellationToken token = source.Token;
    Download3Async("https://www.baidu.com", 100 , token);
}
public async Task Download3Async(string url, int n, CancellationToken cancellationToken)
{
    using (HttpClient client = new HttpClient())
    {
        for (var i = 0; i < n; i++)
        {
            var req = await client.GetStringAsync(url,cancellationToken);
            textBox1.Text = textBox1.Text + (await req.Content.ReadAsStringAsync()).Substring(0, 20);
        }

    }
}

引发的异常:“System.Threading.Tasks.TaskCanceledException”(位于 System.Private.CoreLib.dll 中)

总结:
在ASP.Net Core开发中,一般不需要自己处理CancellationToken、CancellationTokenSource这些,只要做到,能转发CancellationToken就转发即可

二、异步方法6

WhenAll
Task类的重要方法:
1、Task<Task> WhenAny(IEnumerable<Task> tasks)等,任何一个Task完成,Task就完成
2、Task<TResult[]> WhenAll<TResult>(params <TResult[]> tasks)等,所有Task完成才完成。用于等待多个任务执行结束,但是不在乎它们的执行顺序。
3、FromResult()创建普通数值的Task对象。

Task<TResult[]> WhenAll<TResult>(params <TResult[]> tasks),当3个文档都被读完成才算完成。

public async Task GetAllFileChar()
{
    Task<string> t1 = File.ReadAllTextAsync(@"C:\a.txt");
    Task<string> t2 = File.ReadAllTextAsync(@"C:\b.txt");
    Task<string> t3 = File.ReadAllTextAsync(@"C:\c.txt");

    string[] strs = await Task.WhenAll(t1, t2, t3);
    string s1 = strs[0];
    string s2 = strs[1];
    string s3 = strs[2];
    Debug.WriteLine(s1);
    Debug.WriteLine(s2);
    Debug.WriteLine(s3);

}

二、异步方法7

异步与yield
yield return 不仅能够简化数据的返回,而且可以让数据处理流水线化,提示性能

C#的​​迭代器块(yield return)​​ 实现惰性序列生成。C#的​​迭代器块(yield return)​​ 实现惰性序列生成。

static IEnumerable<string> Test()
{
    yield return "hello";
    yield return "hello1";
    yield return "hello2";
}

在旧版C#中,async方法中不能使用yield,从c#8.0开始,把返回值声明为IAsyncEnumerable(不要带Task),然后遍历的时候用await foreach()即可。

private async void button1_Click(object sender, EventArgs e)
{
    await foreach(var s in Test1())
    {
        textBox1.Text = s;
    }
}
static async IAsyncEnumerable<string> Test1()
{
    yield return "hello";
    yield return "hello1";
    yield return "hello2";
}

网站公告

今日签到

点亮在社区的每一天
去签到