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

  1. 打印GetAsync1,进入 Action
  2. 打印GetApiResultModelAsync1,进入 GetApiResultAsync 函数
  3. 打印GetApiResultModelAsync2,GetApiResultAsync 函数主体逻辑执行完毕
  4. 打印GetAsync2, GetApiResultAsync 函数执行完毕,函数继续执行 GetAsync
  5. 打印GetApiResultAsync1,进入 GetApiResultAsync 函数
  6. 打印GetAsync3,由于GetApiResultAsync 中遇到 await,且 HttpClient.GetAsync 是个耗时任务,故将函数控制权交还给 GetAsync,执行 GetAsync 下一步
  7. 打印GetApiResultAsync2,GetApiResultAsync 函数中 HttpClient.GetAsync 执行完毕
  8. 打印GetAsync4,GetAsync 执行完毕

AsyncStateMachine

看完上述流程,大体了解Async/Await 的执行过程,但还是有两个疑问

  1. 线程在await 前后可能发生变化 ex:await client.GetAsync
  2. 在await 时,函数执行的控制权不一定返还给 异步函数的调用者 ex:GetApiResultAsync

为了探索原因,需要探索编译器所作工作。 对上述代码进行反编译.使用工具 ILSpy,取消勾选反编译异步方法即可。
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 函数.

根据反编译的内容,绘制状态机流程图。
状态机流程图
现在可以解答上述问题:

  1. 在await 前后,线程可能发生切换,根据状态加流程图,当 GetAwaiter 后,IsCompleted==false,则 执行 Task.ContinueWith(MoveNext) 且 当前步骤结束,所以可能发生线程切换
  2. 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 一用到底。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#中的async/await是用于处理异步操作的关键字。它们可以帮助开发者更方便地编写和管理异步代码。 首先,我们需要将带有异步操作的方法标记为async,这告诉编译器该方法包含异步代码。然后,我们可以使用await关键字来等待一个异步操作完成。 当遇到await关键字时,程序会暂停执行并等待异步操作完成。在等待期间,控制权会返回给调用者,这样可以避免阻塞线程。一旦异步操作完成,程序会继续执行await后面的代码。 使用async/await可以使异步代码更加易读和易于维护。它们能够简化回调函数和处理异步任务的代码。此外,它们还可以帮助我们处理异常,使得错误处理更加简单。 以下是一个示例,演示了如何使用async/await来调用一个异步方法: ```csharp async Task<string> DownloadDataAsync(string url) { HttpClient client = new HttpClient(); string data = await client.GetStringAsync(url); return data; } async Task Main() { string url = "https://example.com"; string result = await DownloadDataAsync(url); Console.WriteLine(result); } ``` 在上面的示例中,DownloadDataAsync方法使用await关键字等待HttpClient的GetStringAsync方法完成。然后,它将获取的数据作为字符串返回。 在Main方法中,我们通过await关键字等待DownloadDataAsync方法完成,并将结果打印到控制台。 这就是使用async/await处理异步操作的基本概念。希望能对你有所帮助!如果你还有其他问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值