DEV Community

Alex
Alex

Posted on • Edited on

.NET Learning Notes:Asynchronous programming(异步编程)

References:
Microsoft: asynchronous programming
Youtube: YZK

The await keyword provides a non-blocking way to start a task, then continue execution when that task completes.
An important technique for working with asynchronous code: Composing tasks by separating the operations into a new method that returns a task.

Asynchronous model:

  • For I/O-bound code, you await an operation that returns a Task or Task inside of an async method.

  • for CPU-bound code, you await an operation that is started on a background thread with the Task.Run method.

异步方法:用async关键字修饰的方法

  1. 异步方法的返回值一般是Task, T是真正的返回值类型,例如:Task. 惯例:异步方法名字以Async结尾。

  2. 即使方法没有返回值,也最好把返回值声明为非泛型的Task。例如:static asnyc Task main(string[] args)

  3. 调用泛型方法时,一般在方法前面加await关键字,这样拿到的返回值就是泛型制定的T类型。

  4. 异步方法的“传递性”:一个方法中如果有await调用,那么这个方法也必须修饰为async。

  5. the async keyword turns a method into an async method, which allow you to use the await keyword in its body.

  6. async methods need to have an await keyword in their body or they will never yield !!

The method usually includes at least one await expression, which marks a point where the method can't continue until the awaited asynchronous operation is complete. In the meantime, the method is suspened, and control returns to the method's caller.
await 方法的效果等于调用异步方法,然后调用task的Result属性,在编译期,编译器应该就是进行了这样一个操作。
Task's Result property is a blocking property. If you try to access it before its task is finished, the thread that's currently active is blocked until the task completes and the value is available. In most cases, you should access the value by using await instead of accessing the property directly.

`string s = await File.ReadAllTextAsync(@"e:\temp\a\1.txt");
//equivalent to
// Task t = File.ReadAllTextAsync(@"e:\temp\a\1.txt");
// string s = t.Result;

// 没有返回值的调用wait()方法,有死锁的风险`

Asynchronous exceptions:

  • The client code can catch those exceptions when a started task is awaited.

  • When a task that runs asynchronously throws an exception, that task is faulted. Faulted task throw an exception when they're awaited.

Use caution when mixing LINQ with asynchronous code. Because LINQ uses deferred execution, async calls won't happen immediately as they do in a foreach loop unless you force the generated sequence to iterate with a call to .ToList() or .ToArray().
如果同样的功能,既有同步方法,又有同步方法,那么首先使用异步方法。

总结:

  1. async的方法会被C#编译器编译成一个类,会主要根据await调用进行切分为多个状态,对async方法的调用会被拆分为对MoveNext的调用。用await看似是等待,经过编译后,其实没有wait。

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

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

  4. A synchronous method returns when its work is complete, but an async method returns a task value when its work is suspended. When the async method eventually completes its work, the task is marked as completed and the result, if any, is stored in the task.

  5. An async method that has a void return type can't be awaited, and the caller of a void-returning method can't catch any exceptions that the method throws.

async方法缺点:
1.异步方法会生成一个类,运行效率没有普通方法高;
2.可能会占用非常多的线程;

返回值为Task的,不一定都要标注async,标注async只是让我们可以更方便await而已。
如果一个异步方法只是对别的异步方法调用的转发,并没有太多复杂的逻辑(比如等待A的结果,再调用B,再拿返回值处理一些逻辑),那么就可以去掉async关键字。

如果想在异步方法中暂停一段时间,不要用Thread.Sleep(),因为它会阻塞调用线程,而要用await Task.Delay().
有时需要提前终止任务,比如:请求超时、用户取消请求。很多异步方法都有CancellationToken参数,用于提前终止执行的信号。
ASP.NET Core 开发中,一般不要求自己处理CancellationToken、CancellationTokenSource这些,只要做到“能转发Cancellation Token就转发”即可。ASP.NET Core会对用户请求中断进行处理。

Task类的重要方法:

  • Task WhenAny,任何一个Task完成,Task就完成. The await Task.WhenAny doesn't await the finished task. It awaits the Task returned by Task.WhenAny. The result of Task.WhenAny is the task that has completed(or faulted). You should await that task again, even though you know it's finished running. That's how you retrieve its result, or ensure that the exception causing it to fault gets thrown.

  • Task WhenAll,所有Task都完成,Task才算完成

接口中的异步方法:
async是提示编译器为异步方法中的await代码进行分段处理的,而一个异步方法是否修饰了async对于方法的调用者来讲没有区别,因此对于接口中的方法或者抽象方法不能修饰为async。

异步与yield:
yield return不仅能够简化数据的返回,而且可以让数据处理“流水线化”,提升性能。
在旧版C#中,async 方法中不能用yield。从C#8.0开始,把返回值声明为IAsyncEnumerable(不要带Task),然后遍历的时候用await foreach()即可。

Top comments (0)