异步编程
1 原理
注意事项
在使用async和await时,确保不要在async方法中直接抛出异常或者赋值给ref或out类型的参数。以下是一些最佳实践:
- 使用
try-catch块来处理异步方法的异常。 - 不要在
async方法中使用:this,因为当前线程可能已经被另一个线程所占用。 - 不要在
await表达式之前或之后返回任何值或属性。 -
避免阻塞调用:不要在async方法中使用Wait()或Result属性,这会导致死锁或降低性能。
异常处理:async方法中的异常需要在调用者中捕获和处理,通常在await语句之后使用try-catch块。
性能考虑:异步编程对于I/O密集型和长时间运行操作非常有益,但对于CPU密集型任务,同步编程可能更高效。
通过使用async和await,你可以构建响应迅速、性能高效的C#应用程序,特别是当处理网络请求、文件I/O或其他需要等待的外部资源时。






唯一可以在异步中使用的同步机制就是信号量,控制并发数量

异步任务取消


1 第一种取消通过 Whenall 的方式取消,只要有一个任务结束任务就全部结束

2 第二种 通过 传递构造函数取消,超过这个时间就取消任务

第三种 通过设置 CancelAfter的时间来设置取消任务

自定义方法的取消,不要thread.sleep

取消任务后的善后方法 Register

的意义在哪里了,其实只是在进入方法之前判断任务是否取消了,取消了就不执行

异步任务超时
1 线程的超时处理

2 异步中的超时处理,通过whenAll ,先返回的不是我们的任务就是超时

可以将方法写出一个异步超时扩展方法,方便简写代码,这个代码并没有对task进行取消,实际情况还需要对task进行取消

然后用这个方法

Net 6 有自带的超时方法 WaitAsync

管道 Channel,异步不阻塞 生产消费模型

同步方法调用异步方法,比如 构造函数中调用异步方法
方法1

改成扩展方法


方法2 使用 ContinueWith

异步方法中使用同步机制和信号量


这种方法是不行的,lock 表示线程1 进,线程1 出,但是 遇到 await 可能是线程1 进 线程2 出,所以这就不能用lock
1 用第三方类库 不推荐


2 原生 semaphoreslim 信号量

异步方法中更新进度
1普通的更新进度条

2 Progress

优化自定义进度

调用

2 运用
2.1 BlockingCollection 阻塞集合 net4.5 netCore
BlockingCollection 自带阻塞的集合,从而不用手动的去休眠线程
Take 自带阻塞功能,所以不用去手动休眠
//第一个参数表示内部用ConcurrentQueue 作为存储数据 //第二个数据表示只能存放10个 public BlockingCollection<C_Message> mQue = new BlockingCollection<C_Message>(new ConcurrentQueue<C_Message>(),50); public void Test() { var sendThread = new Thread(Send); sendThread.IsBackground=true; var reviceThread = new Thread(Revice); reviceThread.IsBackground =true; sendThread.Start(1); reviceThread.Start(2); //等sendThread 接收完成 sendThread.Join(); ////中断线程 //reviceThread.Interrupt(); //reviceThread.Join(); } private void Send(object obj) { int id = (int)obj; for (int i = 0; i < 20; i++) { mQue.Add(new C_Message() { ID = i }); Debug.WriteLine($"生产=" + i); Thread.Sleep(100); } //CompleteAdding()方法用于指示不再有元素会被添加到队列中,这有助于消费者知道何时停止等待新的元素。 //这在处理有界队列时尤为重要,因为它可以防止消费者无限期地阻塞。 //mQue.CompleteAdding(); } private void Revice(object obj) { int id = (int)obj; try { while (true) //!mQue.IsCompleted { //Take 内部自带信号量,如果没有拿到数据,就会一直等待阻塞在这里(类似 await),一旦队列有数据就会马上拿到数据,这不是轮询是信号量 //如果有数据就会走下一步 var model = mQue.Take(); Debug.WriteLine($"消费=" + model.ID); //Thread.Sleep(1); } } catch (ThreadInterruptedException ) { Debug.WriteLine($"线程中断=" + id); } } } public class C_Message { public int ID { get; set; } }
2.2 Channel Net6
为了解决异步编程的BlockingCollection 阻塞问题 所以出了这个不阻塞集合
//创建一个没有边界的Channel 内部使用的是 ConcurrentQueue
public Channel<C_Message> mChanel = Channel.CreateUnbounded<C_Message>();
//CancellationTokenSource cts = new CancellationTokenSource();
public async void Test()
{
var sendThread = Send(mChanel.Writer,1);
var reviceThread = ReviceSsync(mChanel.Reader,1);
await sendThread;
//mChanel.Writer.Complete();
await Task.Delay(1000);
}
async Task Send(ChannelWriter<C_Message> writer,int id)
{
for (int i = 0; i < 20; i++)
{
await writer.WriteAsync(new C_Message() { ID = i });
Debug.WriteLine($"生产=" + i);
await Task.Delay(100);
}
//CompleteAdding()方法用于指示不再有元素会被添加到队列中,这有助于消费者知道何时停止等待新的元素。
//这在处理有界队列时尤为重要,因为它可以防止消费者无限期地阻塞。
//mQue.CompleteAdding();
}
async Task ReviceSsync(ChannelReader<C_Message> reader, int id)
{
//int id = (int)obj;
try
{
//!reader.Completion.IsCompleted
while (true)
{
//内部维持一个信号量,当没有数据的时候就会一直等待,当获取到数据的时候就会继续
var model = await reader.ReadAsync();
Debug.WriteLine($"消费=" + model.ID);
}
}
catch (OperationCanceledException canExe)
{
Debug.WriteLine($"task 取消" + canExe);
}
}
}
public class C_Message
{
public int ID { get; set; }
}

浙公网安备 33010602011771号