C#学习笔记 -- 异步

0、异步方法

对于 C# 中的 async 和 await,可以这么简单理解:

  • async 告诉 runtime,这个函数可以异步去执行以提高效率。

  • await 则告诉 runtime,真正耗时的是在我这个关键字后面的操作。

  • 不干等,有情况再叫 runtime 过来!哪怕多跑几趟

  • await Task.Run()ConfigureAwait(bool)

    • bool == true当被await修饰的方法执行完毕, 会检查当前runtime线程有没有在干活, 如果干活就等待干完, 接着runtime往下执行

    • bool == false当被await修饰的方法执行完毕, 随机拿个线程继续向下执行, 效率高

  • async修饰的方法

  • 返回值一般是Task<T>

  • 方法名字通常以Async结尾

  • 即使方法没有返回值, 也最好把返回值声明为, 非泛型的Task

  • 调用方法时, 一般在方法前加上await关键字, 这样取得的返回值是泛型指定的T类型

  • 异步方法具有传染性, 一个方法如果有await调用, 则这个方法的修饰符必须为async

static async Task Test()
{
    string str = await GetTextAsync();
}
static async Task<string> GetTextAsync()
{
    return ...
}
  • 类库中如果同步异步方法都存在, 尽量调用异步方法来提高系统并发吞吐量

  • 对于不支持的异步方法, 调用Wait(), Result(), 容易造成死锁的风险

1、异步委托

  • 如果在Lambda表达式中需要调用异步方法, 请在Lambda表达式最前方用async修饰

public void TestAsyncDelegate()
{
    ThreadPool.QueueUserWorkItem(async o =>
    {
        while (true)
        {
            await File.WriteAllTextAsync(@"F:\yzk\a", "aaa");
        }
    });
}

2、异步原理

Thread.CurrentThread.ManagedThreadId获取当前线程Id

  1. async的方法会被C#编译器编译为一个类, 主要根据await调用进行切分

  2. 切分为多个状态, 对async方法的调用会被拆分为对MoveNext的调用

  3. await看似等待, 实则经过编译后, 并没有wait

  4. await调用等待期间, .net会把当前的线程返回给线程池, 等异步方法调用执行完毕后, 框架会从线程池再取出来一个线程执行后续的代码

3、异步与多线程

  • 异步方法的代码并不会自动在新线程中执行, 除非代码放到新线程中执行

await Task.Run() => {
    异步操作;
}

4、async、await异步方法缺点

  1. 异步方法会生成一个类, 运行效率没有普通方法高

  2. 可能会占用非常多的线程

  3. 返回值为Task不一定都要标注async, 标注async只是让我们更方便的await

  4. 去掉async

    1. 如果一个异步方法只是对别的异步方法调用的转发, 并没有太多复杂的逻辑(比如等待A的结果, 再调用B, 把A调用的返回值拿到内部去做一些处理再返回)

(1)去掉async、await方式一: 甩手掌柜

  • 方法0调用方法1返回Task<T>不用返回加await关键字了, 只是普通的方法调用

  • 运行效率更高, 不会造成线程资源浪费

public async Task<string> WithAsync()
{
    string text = await File.ReadAllTextAsync(@"f:\yzk\a\1.txt");
    return text;
}
public Task<string> WithoutAsync()
{
    return File.ReadAllTextAsync(@"f:\yzk\a\1.txt");
}

(2)去掉async、await方式二: 包装为Task返回

  • 使用Task.fromResult(ele)方法, 包装返回值

public async Task<string> WithAsync()
{
    return await Task.Run(() => {
        //操作
        return result;
    });
}
public Task<string> WithoutAsync()
{
    return Task.Run(() => {
        //操作
        return Task.fromResult(result);
    });
}

5、异步方法中的暂停

  • 如果在异步方法中暂停一段时间, 不要用Thread.Sleep(), 会阻塞调用线程

  • 需要使用await Task.Delay()

6、异步方法的取消-CancellationToken结构体

  1. 有时需要提前终止任务, 比如请求超时,用户取消请求

  2. 很多异步方法都有CancellationToken参数, 用于获得提前终止执行的信号

  3. 他是结构体, 有如下成员

    • None, 表示空, 因为结构体没法为空

    • IsCancellationRequested 是否发出了取消任务的请求 bool类型

    • Register(Action callback)注册取消监听

    • ThrowIfCancellationRequested()如果任务被取消, 执行到这句话就抛异常

  4. 通常使用CancellationTokenSource类来构造CancellationToken, 这个类还有如下成员

    • CancellationAfter()超时后发出取消信号

    • Cancel()手动发出取消信号

    • CancellationToken 获取提前结束异步任务令牌

public async static void Main(string[] args)
{
    CancellationTokenSource cts = new CancellationTokenSource();
    cts.CancelAfter(10000);
    CancellationToken token = cts.Token;
    await TestCancellationToken.DonwloadAsync("http://www.baidu.com", 10, token);   
}
  • 从url 下载完n次 或者被暂停, 可以自定义资源取消后续处理

// 从url'下载完'n次或者被暂停, 可以自定义资源取消后续处理
public static async Task DonwloadAsync(string url, int n, CancellationToken cancellationToken)
{
    using (HttpClient client = new HttpClient())
    {
        for (int i = 0; i < n; i++)
        {
            string html = await client.GetStringAsync(url);
            if (cancellationToken.IsCancellationRequested)
            {
                Console.WriteLine("取消了哈");
                break;
            }
        }
    }
}
  • 从url 下载n次 或者被暂停, 不可自定义资源取消后续处理

//从url'下载'n次直到被暂停, 不可用定义
public static async Task DonwloadAsync(string url, int n, CancellationToken cancellationToken)
{
    using (HttpClient client = new HttpClient())
    {
        for (int i = 0; i < n; i++)
        {
            HttpResponseMessage resp = await client.GetAsync(url, cancellationToken);
            string html = await resp.Content.ReadAsStringAsync();
            Console.WriteLine($"{html}");
        }
    }
}

ASP.NET Core中的CancellationToken -- 能传则传, 应转尽转

  • 在asp.net core中, 一般不需要自己处理CancellationToken

  • 只要做到能转发CancellationToken就转发即可,

  • 尽量做到能传则传, 应转尽转

  • asp.net core会对于用户请求中断进行处理

  • 用于用户退出网站, 告知服务器不用继续处理了

//.net core 框架帮我们安排了CancellationToken, 不需要我们自己手动创建
public async Task Index(CancellationToken cancellationToken)
{
    await TestCancellationToken.DonwloadAsync("http://www.baidu.com", 10, token); 
}

6、Task类的重要方法

方法 描述
Task<Task> WhenAny(IEnumberable<Task> task) 任何一个Task完成, Task就完成
Task<TResult[]> WhenAll<TResult>(params Task<Result>[] tasks) 所有Task完成, Task才完成 用于等待多个任务执行结束, 但不在乎他们的执行顺序
FromResult() 创建普通数值的Task对象

1、WhenAll()

public async Task TestTaskWhenAll()
{
    Task<string> t1 = File.ReadAllTextAsync(@"f:\yzk\a\1.txt");
    Task<string> t2 = File.ReadAllTextAsync(@"f:\yzk\a\2.txt");
    Task<string> t3 = File.ReadAllTextAsync(@"f:\yzk\a\3.txt");
    await Task.WhenAll(t1, t2, t3);//3个task完成, Task才完成, 等待多个任务执行完毕不在乎他们的执行顺序
}

例子: 汇总一个文件夹下所有文本文件的单词个数汇总

public async static Task Main()
{
    string[] txts = Directory.GetFiles(@"f:\yzk\a");
    Task<int>[] tasks = new Task<int>[txts.Length];
    for (int i = 0; i < txts.Length; i++)
    {
        Task<int> t = TestTaskWhenAll.GetTxtWords(txts[i]);
        tasks[i] = t;
    }
    int[] txtCounts = await Task.WhenAll(tasks);
    int count = txtCounts.Sum();
    Console.WriteLine($"{count}");    
}
public async static Task<int> GetTxtWords(string url)
{
    string txt = await File.ReadAllTextAsync(url);
    return txt.Length;
}

7、接口中的异步方法不能用async

  • async是提示编译器为异步方法中的await代码进行分段处理的

  • 而一个异步方法是否修饰了async对于方法的调用则来讲是没有区别的

  • 因此对于接口中的方法或者抽象方法不能修饰为async

posted on 2023-10-01 00:10  老菜农  阅读(62)  评论(0编辑  收藏  举报

导航