关键字的真正含义

  1. 当执行返回参数为Task或者Task<>类型的函数时,假如该函数没有用async标识,那么开启新线程执行该方法

  2. 当有async标识时,当前线程会把该方法当成同步函数执行,直到运行到await关键字的地方,开启新线程

  3. await关键字表示会开辟新线程来执行后面的方法,但是该线程会等待新线程执行完返回,然后继续执行

    函数的执行途中是根据await关键字来判断是否需要开辟线程来执行代码(Async void方法调用时不能加await,所以它必定是在主线程中被调用),假如一个async标识的方法里面没有await,那么这个方法会被当成同步方法来调用

Async void的缺点

Async void 主要用于Async event handler,其他时候请不要使用,在async void方法中,一定要加try catch来捕捉异常。

Async void 方法具有不同的错误处理语义。 当 async Task 或 async Task 方法引发异常时,会捕获该异常并将其置于 Task 对象上。 对于 async void 方法,没有 Task 对象,因此 async void 方法引发的任何异常都会直接在 SynchronizationContext(在 async void 方法启动时处于活动状态)上引发。 无法捕获从 async void 方法引发的异常。所以对于Async void方法必须加入try/catch。

Async void 方法具有不同的组合语义。 返回 Task 或 Task 的 async 方法可以使用 await、Task.WhenAny、Task.WhenAll 等方便地组合而成。 返回 void 的 async 方法未提供一种简单方式,用于向调用代码通知它们已完成。 启动几个 async void 方法不难,但是确定它们何时结束却不易。 Async void 方法会在启动和结束时通知 SynchronizationContext,但是对于常规应用程序代码而言,自定义 SynchronizationContext 是一种复杂的解决方案。

Async void 方法难以测试。 由于错误处理和组合方面的差异,因此调用 async void 方法的单元测试不易编写。 MSTest 异步测试支持仅适用于返回 Task 或 Task 的 async 方法。 可以安装 SynchronizationContext 来检测所有 async void 方法都已完成的时间并收集所有异常,不过只需使 async void 方法改为返回 Task,这会简单得多。推荐使用下面方法实现

1
2
3
4
5
6
7
8
9
private async void button1_Click(object sender, EventArgs e)
{
await Button1ClickAsync();
}
public async Task Button1ClickAsync()
{
// Do asynchronous work.
await Task.Delay(1000);
}

应避免混合使用异步代码和阻塞代码。 混合异步代码和阻塞代码可能会导致死锁、更复杂的错误处理及上下文线程的意外阻塞,推荐除了main方法外都使用async方法,不要再异步代码使用Task.Result和Task.Wait。如果一定要在async方法中使用Task.Wait()推荐使用ConfigureAwait(false)来防止死锁。

http://blog.walterlv.com/post/deadlock-in-task-wait.html

Async和匿名函数

假设有这样的方法

1
2
3
4
5
6
7
8
9
10
public async Task ExecuteAwaitAction(Action action){
await Task.Run(action);
await SomethingAsync();
}
public static async void Test(){
await ExecuteAwaitAction(TestAwait);
}
public static async void TestAwait(){
await TestSample();
}

如果我们在main函数里面执行Test()方法的话,SomethingAsync()不会等待Task.Run(action),因为action本身就是异步方法,会立即返回。

如果我们使用async Action会怎么样呢,其实async Action 相当于 async void,async Func相当于async Task

及时停止Task.Delay的方法

Task.Delay(ElapsedMilliseconds, _cancellationTokenSource.Token);而不用Task.Delay(ElapsedMilliseconds); 因为后者会卡住task固定的时常,但是用前者可以随时取消。

一个有参有返回值的方法注释模板

1
2
3
4
5
6
7
8
9
/// <summary>
/// Gets another task which that the given task <paramref name="self"/> can be awaited with a <paramref name="timeout"/>.
/// </summary>
/// <param name="self">The task to be awaited.</param>
/// <param name="timeout">The number of milliseconds to wait.</param>
/// <returns>
/// <c>true</c> if the <see cref="Task"/> completed execution within the allotted time; otherwise, <c>false</c>.
/// </returns>
public static async Task<bool> GetTaskWithTimeout(this Task self, int timeout)

使用CancellationTokenSource创建一个定时轮询的service, 本机测试的是每小时查询一次电压,假如过低就记录日志,并且只记录一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private const int ElapsedMilliseconds = 3600000;private const int StopTaskTimeout = 2000;
private bool _isBatteryLowShown;
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();private Task _checkingStatusTask;

/// <summary>
/// Starts the service.
/// </summary>
public Task StartAsync()
{
_isBatteryLowShown = false;
_checkingStatusTask = RunCheckBatteryStatusPeriodicTask();
return Task.CompletedTask;
}

/// <summary>
/// Stops the service.
/// </summary>
public async Task StopAsync()
{
_cancellationTokenSource.Cancel();
await _checkingStatusTask.GetTaskWithTimeout(StopTaskTimeout);
if (!_checkingStatusTask.IsCompleted)
{
_logger.Warning($"Failed to stop checking status task within {StopTaskTimeout} ms - stopping anyway.");
}
}

private async Task RunCheckBatteryStatusPeriodicTask()
{
try
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
var status = xxx();
if (status && !_isBatteryLowShown)
{
_isBatteryLowShown = true;
_logger.Error("Battery is low.");
}
            
await Task.Delay(ElapsedMilliseconds, _cancellationTokenSource.Token);
}
}
catch (Exception e)
{
_logger.ErrorEx(message: $"{nameof(_checkingStatusTask)} exception.", sourceType: nameof(BatteryMonitoringService), ex: e);
}

}