HTML5技术

C#与C++的发展历程第三 - C#5.0异步编程巅峰 - hystar(10)

字号+ 作者:H5之家 来源:博客园 2016-01-15 10:03 我要评论( )

这里先阐释清创建新线程这个概念。我认为在这种情况下大家说的创建新线程可以被认为是与调用方法使用不同的线程,这个线程可能是线程池已有的,也可能是新建并被加入到线程池的线程。明确这给之后,继续说线程问题

这里先阐释清“创建新线程”这个概念。我认为在这种情况下大家说的“创建新线程”可以被认为是与调用方法使用不同的线程,这个线程可能是线程池已有的,也可能是新建并被加入到线程池的线程。明确这给之后,继续说线程问题。

首先肯定一点async/await关键字不会创建新线程是对的。如上文代码中所示async/await被编译为一个状态机的确不参与Task的创建,实际新建Task的是被调用的异步方法。也就是说每调用一次异步方法(每一个await)都会产生一个新的Task,这个Task会自动执行。前面说过Task由TaskScheduler安排执行,一般都会在一个与调用线程不同的线程上执行。

为了把这个问题解释清楚,假设调用异步方法的线程为A,异步方法启动后在B线程执行。当B线程开始执行后,A线程将交出控制权。异步方法执行结束后,后续代码(await后面的代码)将在B线程上使用A线程的ExecutionContext(和SynchronizationContext,默认情况)继续执行。

注意这个A线程到B线程控制权的转换正是async异步模式的精髓之一。在WPF等这样的客户端环境这样做不会阻塞UI线程,使界面不失去响应。在MVC这样的Web环境可以及时释放HTTP线程,使Web服务器可以接收更多请求。毕竟B线程这种线程池中的线程成本更低。这样就是为什么既然也要花等待异步操作完成的时间,还要另外使用异步方法的原因 - 及时释放调用线程,让低成本的线程去处理耗时的任务。

最后当需要在发起执行的线程(这里是A线程)上继续进行处理时只要获得当时A线程的ExecutionContext和SynchronizationContext就可以了,并在这些Context完成剩余操作即可。

如果后续还有其他await,则会出现C线程,D线程等。如B调用了C的话,B的各种Context会被传递给C。当从异步方法返回后,执行的线程变了但是Context没变。这样异步方法给我们的感觉就像是同步一般。这也就是async/await方法的精妙之处。

那个Task的ConfigureAwait方法又是做什么用的呢,理解了上文就很好理解这个方法了。在异步方法返回时,会发生线程切换,默认情况下(ConfigureAwait(true)时)ExecutionContext和SynchronizationContext都会被传递。如果ConfigureAwait(false)则只有ExecutionContext会被传递,SynchronizationContext不会被传递。在WPF等客户端程序UI部分,应该使用默认设置让SynchronizationContext保持传递,这样异步代码的后续代码才能正常操作UI。除此之外的其他情况,如上面的Service类中,都该使用ConfigureAwait(false)以放弃SynchronizationContext的传递来提高性能。

下面以图应该会对上面这段文字有更深的了解:

吐槽一下,本来是想用vs生成的时序图进行演示呢。结果发现vs2015取消这个功能了。手头也没有其他版本的vs。就用代码截图来掩饰这个线程变化过程吧。

首先是控制台程序的线程变化情况:

图8

因为控制台应用没有SynchronizationContext,所以可以清楚的看到线程的变化。

下面看看在WPF中类似流程执行的样子:

图9

可以看到在默认情况下每个await后的异步代码返回到都回到UI线程,即所有await的后继代码都使用UI线程的SynchronizationContext来执行。除了调用方法外,其它所有的方法没有必要返回UI线程,所以我们应该把除调用开始处(即Button_Click方法)外的所有异步调用都配置为ConfigureAwait(false)。

public partial class MainWindow : Window {     public MainWindow()     {         InitializeComponent();     }     private async void Button_Click(object sender, RoutedEventArgs e)     {         var userService = new Service();         Debug.Write(Thread.CurrentThread.ManagedThreadId);         var avatar = await userService.GetUserAvatarAsync(1);         Debug.Write(Thread.CurrentThread.ManagedThreadId);         //使用获取的avatar     } } public class Service {     private readonly Repository _repository;     private readonly WebHepler _webHelpler;     public Service()     {         _repository = new Repository();         _webHelpler = new WebHepler();     }     public async Task<byte[]> GetUserAvatarAsync(int id)     {         var user = await _repository.GetByIdAsync(id).ConfigureAwait(false);         var email = user.Email;         var avatar = await _webHelpler.GetAvatarByEmailAsync(email).ConfigureAwait(false);         return avatar;     } } public class Repository {     private readonly DbContext _dbContext;     private readonly DbSet<User> _set;     public Repository()     {         _dbContext = new DbContext("");         _set = _dbContext.Set<User>();     }     public async Task<User> GetByIdAsync(int id)     {         //IO...         var user = await _set.FindAsync(id).ConfigureAwait(false);         return user;     } } public class WebHepler {     private readonly HttpClient _httpClient;     public WebHepler()     {         _httpClient = new HttpClient();     }     public async Task<byte[]> GetAvatarByEmailAsync(string email)     {         var url = "http://avater-service-sample/" + email;         var resp = await _httpClient.GetByteArrayAsync(url);         return resp;     } }

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 2年前端学习历程,与找不到工作的悲愤与吐槽!(100%真实经历,看博主怎么一步步走向失业) - 蒋启钲

    2年前端学习历程,与找不到工作的悲愤与吐槽!(100%真实经历,看博

    2017-03-29 11:00

  • 开源第三方登录组件OAuthLogin2.0 支持QQ,阿里巴巴,淘宝,京东,蘑菇街,有赞等平台 - 大壮他哥

    开源第三方登录组件OAuthLogin2.0 支持QQ,阿里巴巴,淘宝,京东,蘑菇街

    2017-01-20 15:00

  • 从零到百亿互联网金融架构发展史 - 纯洁的微笑

    从零到百亿互联网金融架构发展史 - 纯洁的微笑

    2017-01-14 13:00

  • 记一次企业级爬虫系统升级改造(四):爬取微信公众号文章(通过搜狗与新榜等第三方平台) - 彩色铅笔

    记一次企业级爬虫系统升级改造(四):爬取微信公众号文章(通过搜狗

    2017-01-12 10:01

网友点评
>