异步和多线程的关系
实现异步,不一定需要多线程。比如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();
});