C# Async/Await VS Task.Result
由于这段时间工作,使用异步方式调用接口时发生了死锁,故写下这边博客,将之前所学所遇做个整合。
Async/Await
微软推出 Async/Await 的目的是简化异步编程复杂度,将原来程序员需要做的事交给编译器完成,可以使用同步的方式编写异步代码。
Async/Await 的执行过程
图片来源于微软官网,其执行流程可以得出最重要的一点,当方法执行到await 时,执行的控制权交还给异步方法的调用者,等待 getStringTask 完成后才执行当前函数后续内容。
代码验证
// GET api/values
[HttpGet]
[ActionName("GetData")]
public async Task<ApiResultModel> GetAsync()
{
Console.WriteLine("GetAsync1->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
var api = await GetApiResultModelAsync();
Console.WriteLine("GetAsync2->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
var apiTask = GetApiResultAsync();
Console.WriteLine("GetAsync3->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
api.Result = await apiTask;
Console.WriteLine("GetAsync4->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
return api;
}
private async Task<string> GetApiResultAsync()
{
HttpClient client = new HttpClient();
Console.WriteLine("GetApiResultAsync1->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
var res = await client.GetAsync("http://www.baidu.com");
Console.WriteLine("GetApiResultAsync2->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
return res.ToString();
}
private async Task<ApiResultModel> GetApiResultModelAsync()
{
Console.WriteLine("GetApiResultModelAsync1->thread:"+ Thread.CurrentThread.ManagedThreadId.ToString());
var res= await Task.FromResult(new ApiResultModel());
Console.WriteLine("GetApiResultModelAsync2->thread:"+ Thread.CurrentThread.ManagedThreadId.ToString());
return res;
}
由上述代码运行结果可知,主线程调用 GetAsync
- 打印GetAsync1,进入 Action
- 打印GetApiResultModelAsync1,进入 GetApiResultAsync 函数
- 打印GetApiResultModelAsync2,GetApiResultAsync 函数主体逻辑执行完毕
- 打印GetAsync2, GetApiResultAsync 函数执行完毕,函数继续执行 GetAsync
- 打印GetApiResultAsync1,进入 GetApiResultAsync 函数
- 打印GetAsync3,由于GetApiResultAsync 中遇到 await,且 HttpClient.GetAsync 是个耗时任务,故将函数控制权交还给 GetAsync,执行 GetAsync 下一步
- 打印GetApiResultAsync2,GetApiResultAsync 函数中 HttpClient.GetAsync 执行完毕
- 打印GetAsync4,GetAsync 执行完毕
AsyncStateMachine
看完上述流程,大体了解Async/Await 的执行过程,但还是有两个疑问
- 线程在await 前后可能发生变化 ex:await client.GetAsync
- 在await 时,函数执行的控制权不一定返还给 异步函数的调用者 ex:GetApiResultAsync
为了探索原因,需要探索编译器所作工作。 对上述代码进行反编译.使用工具 ILSpy,取消勾选反编译异步方法即可。
public class ValuesController : ApiController
{
[CompilerGenerated]
private sealed class <GetApiResultAsync>d__1 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<string> <>t__builder;
public ValuesController <>4__this;
private HttpClient <client>5__1;
private HttpResponseMessage <res>5__2;
private HttpResponseMessage <>s__3;
private TaskAwaiter<HttpResponseMessage> <>u__1;
private void MoveNext()
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Expected O, but got Unknown
int num = <>1__state;
string result;
try
{
TaskAwaiter<HttpResponseMessage> awaiter;
if (num != 0)
{
<client>5__1 = new HttpClient();
Console.WriteLine("GetApiResultAsync1->thread:" + Thread.CurrentThread.ManagedThreadId);
awaiter = <client>5__1.GetAsync("http://www.baidu.com").GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<GetApiResultAsync>d__1 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter<HttpResponseMessage>);
num = (<>1__state = -1);
}
<>s__3 = awaiter.GetResult();
<res>5__2 = <>s__3;
<>s__3 = null;
Console.WriteLine("GetApiResultAsync2->thread:" + Thread.CurrentThread.ManagedThreadId);
result = ((object)<res>5__2).ToString();
}
catch (Exception exception)
{
<>1__state = -2;
<client>5__1 = null;
<res>5__2 = null;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<client>5__1 = null;
<res>5__2 = null;
<>t__builder.SetResult(result);
}
}
[CompilerGenerated]
private sealed class <GetApiResultModelAsync>d__2 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<ApiResultModel> <>t__builder;
public ValuesController <>4__this;
private ApiResultModel <res>5__1;
private ApiResultModel <>s__2;
private TaskAwaiter<ApiResultModel> <>u__1;
private void MoveNext()
{
int num = <>1__state;
ApiResultModel result;
try
{
TaskAwaiter<ApiResultModel> awaiter;
if (num != 0)
{
Console.WriteLine("GetApiResultModelAsync1->thread:" + Thread.CurrentThread.ManagedThreadId);
awaiter = Task.FromResult(new ApiResultModel()).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<GetApiResultModelAsync>d__2 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter<ApiResultModel>);
num = (<>1__state = -1);
}
<>s__2 = awaiter.GetResult();
<res>5__1 = <>s__2;
<>s__2 = null;
Console.WriteLine("GetApiResultModelAsync2->thread:" + Thread.CurrentThread.ManagedThreadId);
result = <res>5__1;
}
catch (Exception exception)
{
<>1__state = -2;
<res>5__1 = null;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<res>5__1 = null;
<>t__builder.SetResult(result);
}
}
[CompilerGenerated]
private sealed class <GetAsync>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<ApiResultModel> <>t__builder;
public ValuesController <>4__this;
private ApiResultModel <api>5__1;
private Task<string> <apiTask>5__2;
private ApiResultModel <>s__3;
private ApiResultModel <>s__4;
private string <>s__5;
private TaskAwaiter<ApiResultModel> <>u__1;
private TaskAwaiter<string> <>u__2;
private void MoveNext()
{
int num = <>1__state;
ApiResultModel result;
try
{
TaskAwaiter<string> awaiter;
TaskAwaiter<ApiResultModel> awaiter2;
if (num != 0)
{
if (num == 1)
{
awaiter = <>u__2;
<>u__2 = default(TaskAwaiter<string>);
num = (<>1__state = -1);
goto IL_0177;
}
Console.WriteLine("GetAsync1->thread:" + Thread.CurrentThread.ManagedThreadId);
awaiter2 = <>4__this.GetApiResultModelAsync().GetAwaiter();
if (!awaiter2.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter2;
<GetAsync>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
return;
}
}
else
{
awaiter2 = <>u__1;
<>u__1 = default(TaskAwaiter<ApiResultModel>);
num = (<>1__state = -1);
}
<>s__3 = awaiter2.GetResult();
<api>5__1 = <>s__3;
<>s__3 = null;
Console.WriteLine("GetAsync2->thread:" + Thread.CurrentThread.ManagedThreadId);
<apiTask>5__2 = <>4__this.GetApiResultAsync();
Console.WriteLine("GetAsync3->thread:" + Thread.CurrentThread.ManagedThreadId);
<>s__4 = <api>5__1;
awaiter = <apiTask>5__2.GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 1);
<>u__2 = awaiter;
<GetAsync>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
goto IL_0177;
IL_0177:
<>s__5 = awaiter.GetResult();
<>s__4.Result = <>s__5;
<>s__4 = null;
<>s__5 = null;
Console.WriteLine("GetAsync4->thread:" + Thread.CurrentThread.ManagedThreadId);
result = <api>5__1;
}
catch (Exception exception)
{
<>1__state = -2;
<api>5__1 = null;
<apiTask>5__2 = null;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<api>5__1 = null;
<apiTask>5__2 = null;
<>t__builder.SetResult(result);
}
}
[AsyncStateMachine(typeof(<GetAsync>d__0))]
[DebuggerStepThrough]
[HttpGet]
[ActionName("GetData")]
public Task<ApiResultModel> GetAsync()
{
<GetAsync>d__0 stateMachine = new <GetAsync>d__0();
stateMachine.<>t__builder = AsyncTaskMethodBuilder<ApiResultModel>.Create();
stateMachine.<>4__this = this;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
[AsyncStateMachine(typeof(<GetApiResultAsync>d__1))]
[DebuggerStepThrough]
private Task<string> GetApiResultAsync()
{
<GetApiResultAsync>d__1 stateMachine = new <GetApiResultAsync>d__1();
stateMachine.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
stateMachine.<>4__this = this;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
[AsyncStateMachine(typeof(<GetApiResultModelAsync>d__2))]
[DebuggerStepThrough]
private Task<ApiResultModel> GetApiResultModelAsync()
{
<GetApiResultModelAsync>d__2 stateMachine = new <GetApiResultModelAsync>d__2();
stateMachine.<>t__builder = AsyncTaskMethodBuilder<ApiResultModel>.Create();
stateMachine.<>4__this = this;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
}
之前的代码反编译后生成了 三个实现 IAsyncStateMachine 接口的私有类,分别对应了 GetAsync,GetApiResultAsync,GetApiResultModelAsync 函数.
根据反编译的内容,绘制状态机流程图。
现在可以解答上述问题:
- 在await 前后,线程可能发生切换,根据状态加流程图,当 GetAwaiter 后,IsCompleted==false,则 执行 Task.ContinueWith(MoveNext) 且 当前步骤结束,所以可能发生线程切换
- await 时,函数执行控制权不一定交还给异步方法调用者,情况和1 相反(IsCompleted==true)
Task.Result
上述铺叙了那么多,还得回到工作遇到的问题本身
发生死锁
请看下面一段代码机器执行结果:
// GET api/values
[HttpGet]
[ActionName("GetData")]
public async Task<ApiResultModel> GetAsync()
{
Console.WriteLine("GetAsync1->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
var api = await GetApiResultModelAsync();
Console.WriteLine("GetAsync2->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
var apiTask = GetApiResultAsync();
Console.WriteLine("GetAsync3->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
api.Result = apiTask.Result;//产生死锁
Console.WriteLine("GetAsync4->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
return api;
}
private async Task<string> GetApiResultAsync()
{
HttpClient client = new HttpClient();
Console.WriteLine("GetApiResultAsync1->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
var res = await client.GetAsync("http://www.baidu.com");
Console.WriteLine("GetApiResultAsync2->thread:"+Thread.CurrentThread.ManagedThreadId.ToString());
return res.ToString();
}
private async Task<ApiResultModel> GetApiResultModelAsync()
{
Console.WriteLine("GetApiResultModelAsync1->thread:"+ Thread.CurrentThread.ManagedThreadId.ToString());
var res= await Task.FromResult(new ApiResultModel());
Console.WriteLine("GetApiResultModelAsync2->thread:"+ Thread.CurrentThread.ManagedThreadId.ToString());
return res;
}
结果:
代码和之前唯一的区别就是 由 await apiTask 替换为 apiTask.Result,从打印结果来看,缺少 GetAsync4 和 GetApiResultAsync2 的打印,说明函数未执行到此处。
死锁产生原因
Task.Result 和 await Task 的区别
由上述内容可知,await 并不是阻塞当前线程,而是 根据 GetAwaiter() 的结果判定是否需要将函数执行的控制权交还给异步函数调用者(若一个需要长时间执行的任务,则此处归还控制权).
而Task.Result 阻塞当前线程直至 Task Complete
SynchronizationContext
SynchronizationContext
根据文档描述,当前 SynchronizationContext 在一个等待点捕获(await),此 SynchronizationContext 用于在等待后继续(更确切地说,仅当它不为 null 时,才捕获当前 SynchronizationContext,如果为 null,则捕获当前 TaskScheduler).
原因
当 使用Task.Result 时,阻塞当前线程,等待Task 完成,但此时Task 在 TaskScheduler 队列中排队等待 Task.Result 完成,故此处造成死锁。
解决方案
建议使用async/await 一用到底。