首页 程序笔记 C#中await/async异步编程采坑—async方法可能会同步执行

C#中await/async异步编程采坑—async方法可能会同步执行

前言

在C# 5.0和.NET 4.5中,引入了基于await/async的异步编程模式,也称为“基于任务的异步编程模型 (TAP) ”。它有效地避免了异步任务回调嵌套的地狱,而且非常易于使用,但是深度理解它却比学会使用它要困难得多。

异步方法被同步执行的几种场景

await/async的异步方法一般会被分配到线程池中运行,也可以设置为启动新的线程执行,它一般不会阻塞当前调用的线程,例如:

async void DelayAsync()
{
    await Task.Delay(100);
}

void Delay()
{
    Thread.Sleep(100);
}

我们把Delay方法看作某一个耗时的方法,而DelayAsync可以算是它的异步版本,那么,在一个窗体程序中分别调用这两个方法:

private void button1_Click(object sender, EventArgs e)
{
    for (int i = 0; i  50; i++)
    {
        DelayAsync();
    }
}

private void button2_Click(object sender, EventArgs e)
{
    for (int i = 0; i  50; i++)
    {
        Delay();
    }
}

从以上代码的执行会发现,点击button2时UI线程阻塞了5秒钟,而点击button1时则完全没有异样。
那么问题来了:async方法一定就会异步执行而不会阻塞线程吗?
答案是否定的!!!
为了解释async方法如何阻塞线程,下面我们构造一些场景来看这个问题。

同步执行场景一

考虑下面的FakeDelayAsync方法

async void FakeDelayAsync()
{
    Delay();
}

以上代码中,使用async关键字标记上了FakeDelayAsync方法,但内部实现却是一个同步的Delay方法。
调用这个方法时,会有两种可能:

  • ×系统将Delay方法插入线程池执行,当前线程不会被阻塞,因为这个方法是async方法。
  • √系统直接调用Delay,当前线程阻塞。
    实际执行一下,会发现执行FakeDelayAsync和直接执行Delay的表现是一样的,两者都是同步执行。实际上,在VisualStudio中打出这个方法时,就会有警告:

此异步方法缺少 “await” 运算符,将以同步方式运行。请考虑使用 “await” 运算符等待非阻止的 API 调用,或者使用 “await Task.Run(…)” 在后台线程上执行占用大量 CPU 的工作。

同步执行场景二

考虑下面的FakeDelayAsync2和FakeDelayAsync3方法

async Task FakeDelayAsync2()
{
    Delay();
}

async void FakeDelayAsync3()
{
    await FakeDelayAsync2();
}

在FakeDelayAsync3方法中,VisualStudio没有任何警告,那么在调用这个方法时,会有两种可能:

  • ×系统将FakeDelayAsync2方法插入线程池执行,当前线程不会被阻塞,因为这个方法是async Task。
  • √系统直接调用Delay,当前线程阻塞。
    实际执行以上代码,就会发现FakeDelayAsync3和Delay的表现还是一样的。即便VisualStudio在FakeDelayAsync3中没有给出任何的警告,但它调用FakeDelayAsync2方法仍然是同步执行的!!!

同步执行场景三

也许你会认为,FakeDelayAsync3中虽然没有警告,但FakeDelayAsync2中也有啊!那么,考虑下面的情形:

async Task HalfFakeDelayAsync(bool isAsync)
{
    if (isAsync)
    {
        Delay();
    }
    else
    {
        await Task.Delay(100);
    }
}

async void FakeDelayAsync4()
{
    await HalfFakeDelayAsync(true);
}

构建一个存在await关键字的HalfFakeDelayAsync方法,再在FakeDelayAsync4中调用它。
那么,调用FakeDelayAsync4方法时,即便VisualStudio完全没有给出任何警告,但是它依然是同步执行。

同步执行场景四

你也许会认为,上面的几个场景都是别有用心的构造出来的,或是因为“错误的”编程而导致async方法被同步执行的,那么,我们可以考察下面这个完全使用.NET Framework所提供async方法的例子:

async void FakeDelayAsync5()
{
    SemaphoreSlim semaphore = new SemaphoreSlim(int.MaxValue);
    for (int i = 0; i  10000000; i++)
    {
        await semaphore.WaitAsync();
    }
}

这个FakeDelayAsync5方法,仍然是同步执行的!!!

总结

总结以上四个场景,实际上,我们可以认为:

  • 不论是async void还是async Task,不论VisualStudio有没有给出警告,都有可能以同步的方式执行,从而阻塞调用线程。
  • 一个async void或者async Task究竟是否会以异步方式执行,取决于其本身内部逻辑是否会释放当前线程。
  • 因此,被标记为async的方法,仅仅是异步的一个必要不充分条件。
2

站心网

前言 在C# 5.0和.NET 4.5中,引入了基于await/async的异步编程模式,也称为“基于任务的异步编程模型 (TAP)..

为您推荐

软件产品开发中常见的10个问题及处理方法

常见的10个问题#产品开发中常见的10个问题思维导图需求相关#1. 需求不明确#在日常工作中,需求来源于用户、老板、客户、竞品分析、业务部门、产品经理等,这些人或部门会提出需求,因为他们不是产品经理,提出的需求..

一个提升运营/营销转化率的万能方法!

之前,笔者有分享过说我最喜欢的增长是可复制可持续的增长,它一般都会有三个关键过程:首先是小范围测试,低成本试错,也就是最小可行性验证,让ROI最大化。然后是把测试的最优结果整理为可执行的标准化化流程。前..

学习最重要是方法,管理最重要是高度

技术学习要兼顾深度和广度夯实技术基础这么多年来,我面试了很多人。我越发感到「技术基础」非常重要。很多技术的本质是一样的。技术基础足够好的话,学东西可以非常快。往下到操作系统层面,甚至计算机硬件层面,你..

Java中String类常见的方法

以下介绍字符串常见的几个方法。介绍String类在 Java 中,String类是一个代表字符串的类,具有以下特性:不可变性:String对象一旦被创建就是不可变的,即它们的值在创建后不能被更改。任何对String对象的修改操作实..

.net 通过 HttpClient 下载文件同时报告进度的方法

通过 HttpClient 的 ContentLength 很多时候都可以拿到下载的内容的长度,通过 ReadAsync 可以返回当前读到的长度,将读取到的长度加起来就是已经下载的长度看起来很简单,于是直接给代码private static async Task ..

javascript 6种连接数组的方法和对比

在 JavaScript 中,有多种方法可以用来连接数组,以下是常见的几种:1. concat() 方法用于连接两个或多个数组,返回一个新的数组,不修改原数组。const arr1 = [1, 2, 3];const arr2 = [4, 5, 6];const result = arr..

.NET开源ORM FreeSql常见问题和解决方法

FreeSql 是一个功能强大的 .NET 开源 ORM(对象关系映射)工具,支持多种数据库类型并提供丰富的功能特性。以下是使用 FreeSql 过程中常见的问题及其解决方法:1. 数据库连接问题问题描述:无法连接数据库,报错提示..

js 数组方法 - 修改篇

js数组方法可分为三类:修改、访问、迭代。修改:改变原有数组访问:不改变原有数组,从原有数组中获取数据迭代:对数组的每一项进行处理,返回数据本文主要介绍数组的修改篇一、增加1、push()含义:在数组末尾添加..

sourcetree安装跳过注册方法

SourceTree下载提取码: ni9m 需翻墙或者破解注册当前只有Win的版本,Mac自行百度很多人用git命令行不熟练,那么可以尝试使用sourcetree进行操作。然鹅~~sourcetree又一个比较严肃的问题就是,很多人不会跳过注册或者..

.NET开发中常见的异常报错原因和解决方法?

在 .NET 开发中,常见的报错通常涉及代码编写、配置和运行时环境。以下是一些常见的报错类型及其解决建议:1. NullReferenceException原因:尝试访问空对象的成员。解决:在访问对象之前检查是否为 null,可以使用 C..

修改VisualSVN Server地址为ip地址,修改svn服务端地址为ip或者域名地址的方法

svn服务端搭建成功之后,地址太长很麻烦,想搞一个服务器专门做svn服务端,修改svn地址为ip地址无奈网上教程不靠谱,于是自己研究了下1.修改VisualSVN 的地址2修改地址并保存很多人不成功就在这里,点击确认之后复制..

Angular UT 模拟执行setTimeout

在 Angular 单元测试中,我们经常需要模拟异步操作,比如 setTimeout。提高测试速度: 真实环境下的 setTimeout 会阻塞测试,导致测试运行时间过长。确保测试的可靠性: 模拟 setTimeout 可以让我们更好地控制异步操..

Angular Mock 一个类的静态方法

理解 Angular Mock 和 静态方法在 Angular 单元测试中,Angular Mock 是一个强大的工具,它允许我们模拟和替换 Angular 的服务、组件和其他依赖项。而静态方法 是属于类本身,而不是类的实例的方法。它们可以直接通..

Redis 同步、击穿、穿透及雪崩简述

对Redis最常见的几个问题,简要的说下我的理解与解决方法。数据同步指Redis做为缓存,在数据变化时,怎么保持与数据库数据同步的。一般解决方案为:缓存双删(同步方案大都采用删除缓存,而不会更新新缓存。缓存击穿..

C# 使用Barrier进行多线程同步

在多线程编程中,同步是一个关键问题。Barrier 是 .NET 提供的一种同步机制,用于协调多个线程在执行某个阶段工作时进行等待,直到所有参与的线程都达到某个同步点后再继续执行。这对于需要在多个线程之间进行阶段性..

解决.NET JAVA PHP中写入及读取memcache中数据不一致的方法

解决.NET JAVA PHP中写入及读取memcache中数据不一致的方法。公司部分接口使用PHP开发,部分接口使用.NET开放,.NET使用redis,PHP使用memcache作为分布式缓存服务器,但是部分业务需要.NET读取memcache里的数据。m..

AutoMapper使用方法

什么是AutoMapper? 一些orm框架,在用到Entity的时候有一些开源代码用到了automapper,将数据对象转成DTO。比如在ORM中,与数据库交互用的Model模型是具有很多属性。而当我们与其它系统(或系统中的其它结构)进行..

.NET调试Windows服务的方法

很多朋友编写Windows服务的时候都会觉得调试很麻烦,甚至不知道怎么调试。有些人可能添加个windows窗体用按键触发相关方法或者靠打印日志调试,那么到底windows服务怎么调试呢? 怎么编写代码就不说了。就说调试吧,..

c# ThreadPool使用方法

ThreadPool类提供一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。 线程池通过为应用程序提供一个由系统管理的辅助线程池使您可以更为有效地使用线程。一个线程监视排到线程..

WebClient上传文件方法UploadFile

测试环境 服务器端:Windows Server 2003,IIS6.0. 上传文件的代码: [csharp] view plaincopy在CODE上查看代码片派生到我的代码片 using (WebClient client = new WebClient() { Credentials = CredentialCache.De..

发表回复

返回顶部