C# 异步编程

异步编程是一种思路
异步相当于对线程池的封装
await相当于让另一个线程来干这个事	



常见概念

已经有多线程了,为何还要异步

多线程与异步是不同的概念

异步并不意味着多线程,单线程同样可以异步
异步默认借助线程池
多线程经常阻塞,而异步要求不阻塞

多线程与异步的适用场景不同

多线程
适合CPU密集型操作
适合长期运行的任务
线程的创建与销毁开销较大
提供更底层的控制,操作线程、锁、信号量等
线程不易于传参及返回
线程的代码书写较为繁琐
异步
适合IO密集型操作
适合短暂的小任务
避免线程阻塞,提高系统响应能力

什么是异步任务(Task)

包含了异步任务的各种状态的一个引用类型

正在运行、完成、结果、报错等

public class TODO {
    public static event Func<object, string> foo;

    static void Main() {

        Task<string> task = new Task<string>((n) => {
            Thread.Sleep(1500);
            for (int i = 0; i < (int)n; i++) {
                Console.WriteLine("DONE {0}", i);
            }
            return "ok";
        }, 3);

        Console.WriteLine(task.Status);
        task.Start();
        Console.WriteLine(task.Status);
        Thread.Sleep(1000);
        Console.WriteLine(task.Status);
        Thread.Sleep(2000);
        Console.WriteLine(task.Status);
        Console.WriteLine(task.Result);
    }
}

输出结果

Created
WaitingToRun
Running
DONE 0
DONE 1
DONE 2
RanToCompletion
ok
另有ValueTask值类型版本

对于异步任务的抽象

开启异步任务后,当前线程并不会阻塞,而是可以去做其他事情
异步任务(默认)会借助线程池在其他线程上运行
获取结果后回到之前的状态

任务的结果

返回值为Task的方法表示异步任务没有返回值
返回值为Task<T>则表示有类型为T的返回值

异步方法(async Task)

将方法标记async 后,可以在方法中使用await关键字

await关键字会等待异步任务的结束,并获得结果

async + await 会将方法包装成状态机,await类似于检查点

MoveNext方法会被底层调用,从而切换状态
async Task
返回值依旧是Task类型,但是在其中可以使用await关键字
在其中写返回值可以直接写Task<T> 中的T类型,不用包装成Task<T>

async void

同样是状态机,但缺少记录状态的Task对象
无法聚合异常(Aggregate Exception),需要谨慎处理异常
几乎只用于对于事件的注册

*异步编程具有传染性(Contagious)
*
一处async,处处async
几乎所有自带方法都提供了异步的版本


重要思想:不阻塞!

await会暂时释放当前线程,使得该线程可以执行其他工作,而不必阻塞线程直到异步操作完成

不要在异步方法里用任何方式阻塞当前线程

常见阻塞情形

Task.Wait() & Task.Result

如果任务没有完成,则会阻塞当前线程,容易导致死锁

Task.GetAwaiter().GetResult(),不会将Exception 包装为AggregateException

Task.Delay() vs. Thread.Sleep()

后者会阻塞当前的线程,这与异步编程的理念不符
前者是一个异步任务,会立刻释放当前的线程

IO等操作的同步方法

其他繁重且耗时的任务


同步上下文

一种管理和协调线程的机制,允许开发者将代码的执行切换到特定的线程

WinFormsWPF拥有同步上下文(UI 线程),而控制台程序默认没有

ConfigureAwait(false)
配置任务通过await方法结束后是否会到原来的线程,默认为true
一般只有UI线程会采用这种策略

TaskScheduler

控制Task的调度方式和运行线程

线程池线程Default
当前线程CurrentThread
单线程。上下文STAThread
长时间运行线程LongRunning

优先级、上下文、执行状态等


一发即忘(Fire-and-forget)

调用一一个异步方法,但是并不使用await或阻塞的方式去等待它的结束
无法观察任务的状态(是否完成、是否报错等)



简单任务

如何创建异步任务?

Task.Run()
Task.Factory.StartNew()
提供更多功能,比如TaskCreationOptions.L ongRunning
Task.Run相当于简化版
new Task + Task.Start()
很少有创建一个Task却没有让他立刻开始的
例子
public class TODO {

    static async Task Main() {
        
        Console.WriteLine(12);
        Console.WriteLine("ThreadId" + Environment.CurrentManagedThreadId.ToString());
        var task = await Task.Run(heavyJob);
        //Console.WriteLine(task);
        Console.WriteLine(123);
    }
    public static int heavyJob() {
        Console.WriteLine("ThreadId" + Environment.CurrentManagedThreadId.ToString());
        Thread.Sleep(10);
        return 1;
    }


}


如何同时开启多个异步任务?

public class TODO {
    static async Task Main() {
        var inputs = Enumerable.Range(10, 10).ToArray();
        var tasks = new List<Task<int>>();
        Console.WriteLine(Environment.CurrentManagedThreadId);
        foreach (var input in inputs) {
            tasks.Add(foo(input));
        }
        await Task.WhenAll(tasks);
        var outputs = tasks.Select(x => x.Result).ToArray();
        foreach (var output in outputs) {
            Console.WriteLine(output);
        }
    }
    public static async Task<int> foo(int input) {
        await Task.Delay(5000);
        return input * 2;
    }
}

任务如何取消?

CancellationTokenSource + CancellationToken

public class TODO {
    static async Task Main() {
        var cts = new CancellationTokenSource();
        try {
            var task = Task.Delay(100000, cts.Token);
            Thread.Sleep(2000);
            cts.Cancel();//抛出异常
            await task;
        } catch (TaskCanceledException) {
            Console.WriteLine("ss");
        } finally {
            cts.Cancel();
        }
    }
}

OperationCanceledException & TaskCanceledException

推荐异步方法都带上CancellationToken这一传参

你自己写了异步方法却不支持传入这个————我可以不用,但不能没有

任务超时如何实现?


在异步任务中汇报进度?


如何在同步方法中调用异步方法?



常见的误区

异步一定是多线程?

异步编程不必需要多线程来实现

比如可以在单个线程上使用异步I/O 或事件驱动的编程模型(EAP)

单线程异步:自己定好计时器,到时间之前先去做别的事情
多线程异步:将任务交给不同的线程,并由自己来进行指挥调度


异步方法一定要写成async Task?

async关键字只是用来配合await 使用,从而将方法包装为状态机

本质上仍然是Task,只不过提供了语法糖,并且函数体中可以直接return Task的泛型类型

接口中无法声明async Task


await一定会切换同步上下文?

在使用await关键字调用并等待一个异步任务时,异步方法不一定会立刻来到新的线程上

如果await了一个已经完成的任务(包括Task.Delay(0)),会直接获得结果


异步可以全面取代多线程?

异步编程与多线程有一定关系,但两者并不是可以完全互相替代


Task.Result 一定会阻塞当前线程?

如果任务已经完成,那么Task.Result 可以直接得到结果


开启的异步任务一定不会阻塞当前线程?

await关键字不一定会立刻释放当前线程,所以如果调用的异步方法中存在阻塞(如Thread.Sleep(O))
那么依旧会阻塞当前上下文对应的线程



同步机制

传统方法

Monitor(lock)
Mutex
Semaphore
EventWaitHandle

轻量型

所有只有SemaphoreSlim不阻塞
ManualResetEventSlim

并发集合

第三方库

AsyncManulResetEvent

Miccrosoft.VisualStudio.Threading

AsyncLock

Nito.AsyncEx
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
C#异步编程中,正确释放资源非常重要,以确保程序的性能和稳定性。以下是一些处理异步编程资源释放的最佳实践: 1. 使用`using`语句:对于实现了`IDisposable`接口的资源,可以使用`using`语句来自动释放资源。`using`语句在代码块结束时会自动调用资源的`Dispose`方法。 ```csharp using (var resource = new SomeDisposableResource()) { // 使用资源的代码 } ``` 2. 取消异步操作:如果异步操作涉及到需要手动释放的资源,可以考虑在取消异步操作时进行资源释放。使用`CancellationTokenSource`来取消异步操作,并在取消回调中释放资源。 ```csharp private CancellationTokenSource cts = new CancellationTokenSource(); async Task MyAsyncMethod() { try { await SomeAsyncOperation(cts.Token); } catch (OperationCanceledException) { // 处理取消操作 } finally { // 释放资源 // ... } } void CancelAsyncOperation() { cts.Cancel(); } ``` 3. 使用`TaskCompletionSource`:如果异步操作依赖于需要手动释放的资源,可以使用`TaskCompletionSource`来包装异步操作,并在任务完成时释放资源。 ```csharp Task SomeAsyncOperationWithResource() { var tcs = new TaskCompletionSource<object>(); using (var resource = new SomeResource()) { // 异步操作代码 // ... // 异步操作完成后释放资源 resource.Dispose(); tcs.SetResult(null); } return tcs.Task; } ``` 通过合理使用`using`语句、取消异步操作和`TaskCompletionSource`,可以确保在异步编程中正确释放资源,避免资源泄漏和性能问题。根据具体情况选择合适的方法来处理资源释放,以确保程序的可靠性和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

emplace_back

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值