C#:异步

84 阅读3分钟

异步和多线程的关系

实现异步,不一定需要多线程。比如js就是单线程异步语言。setTimeout,setInterval等都是异步执行回调,而且只在主线程中执行。

setTimeout(function () {
	tim.print('Hello\n');
}, 100);

setTimeout将在100毫秒后异步调用匿名函数function 并打印出“Hello”。

但是如果将主线程阻塞,那么setTimeout将永远也无法异步执行回调了。

简述以下setTimeout原理,当执行到setTimeout函数时,setTimeout函数底层将回调匿名函数function加入事件队列(或者链表或者树,等等什么数据结构都可以,JavaScript引擎采用事件队列)。

当主线程执行的代码完毕时,主线程直接进入事件循环,事件循环会每隔一段时间(此回答中的JavaScript是间隔1毫秒),看看有没有异步回调可以执行了,setTimeout已将function加入队列,所以时间到了,条件达成,就可以调用function了,因为事件循环还是在主线程中完成的,所以还是主线程执行的异步函数function。

异步方法

异步方法的返回值是泛型Task<T>

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

注意

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

那么异步方法,既然没有开启多线程,怎么实现异步的呢?不知道

  • 如果同样的功能,既有同步方法,又有异步方法,那么首先使用异步方法
  • 异步方法最好以async结尾。但不是必须async修饰。如果一个异步方法只是对别的异步方法调用的转发,并没有太多复杂的逻辑,那么就可以去掉async关键字。
  • 异步方法具有传染性,一个方法中如果有await调用,则这个方法也必须修饰为async异步方法
  • 即使一个异步方法没有返回值,也不要把返回值写为void(否则会有很多问题),要写为非泛型的Task。这时代码中不需要return
  • 如果在异步方法中暂停一段时间,不要用Thread.Sleep。因为它会阻塞调用线程(可能阻塞主线程),降低并发。而要用await Task.Delay(),这个不会阻塞主线程,而是在异步方法编译成的类中阻塞moveNext
//暂停3s
await Task.Delay(3000)
  • 接口中的方法或者抽象方法不能修饰为async。因为async是提示编译器为异步方法中的await代码进行分段处理的,而一个异步方法是否修饰了async对于方法的调用者是没有区别的
interface ITest {
    //不需要async修饰
    Task<int> GetCharCount(string file);
}
class Test : ITest {
    public async Task<int> GetCharCount(string file) {
        string s = await File.ReadAllTextAsync(file);
        return s.Length;
    }
}

异步委托

ThreadPool.QueueUserWorkItem(async(obj) => {
   await SomeAsync();
 });