在开始吹牛之前,老周说两个故事。

第一个故事是关于最近某些别有用心的人攻击.net的事,其实我们不用管它们,只要咱们知道自己是.net爱好者就行了,咱们就是因为热爱.net才会选择它。这些人在这段时间攻击.net,估计和.net的开源、跨平台有关,并且,据说VS 2015 Update 1会进一步深化和扩展全平台,估计有些人是沉不住气了,毕竟他们用的开发工具是比VS落后了四个多世纪的。最近又出了个Visual Studio Dev Essentials计划。

所以嘛,对于这些人,我把林妹妹的一首诗送给他们:

无端弄笔是何人?

作践.net太轻狂。

不悔自家无见识,

却将丑语怪他人。

接下来说说第二个故事。或许不少应届毕业生都在准备或者已经在找实习单位,或找工作了,于是有朋友私信老周,希望老周说说简历如何做的事情。这个嘛,一来,老周不是简历专家;二来,在本文中不好展开去谈,过一两天吧,老周找时间再写篇烂文,专门说说这个事;三来,仅为一家之言,以供参考。

============================================

好了,与主题无关的话说完了,下面开始说正事。记得在X月前,老周写过有关ThreadLocal的文章,也就是基于线程的本地变量存储。使用这个ThreadLocal的前提是:

1、变量必须是多个线程共享的,如果是线程范围内的局部变量就不需要了。

2、希望每个线程都能读写独立的变量值。

今天,老周再介绍一个功能和ThreadLocal类似的东东——AsyncLocal。

这个主要是用于保存异步等待上下文中的共享变量的值。从C# 5开始,引入了相当简便的异步等待语法,即await关键字调用异步方法,允许异步等待。

即代码在使用await关键字调用异步方法后,当前程序会等待异步方法返回后才会继续执行,但在这个等待过程中,不会阻塞当前线程,这比起编写委托来回调方便多了。

异步方法是基于Task的自动线程调度,在异步上下文的切换过程中,有可能会导致数据丢失。比如,在await调用前,对某个变量赋了值,而这个变量是多个线程共享的;当await调用返回后,有可能当前代码仍然处于先前的线程上,但也有可能被调度到其他线程上。这种情况一般发生在与应用程序UI线程无关的代码上,如果异步操作是由UI启动的,通常情况下不会调动异步上下文的线程,然而,如果异步操作是非UI触发的,典型的如在Main入口处启动的,这就很有可能出现异步上下文处于不同的线程上的情形。

这样描述太抽象,很难懂,没事,给大家看一个例子就知道了。

先定义一个异步方法:

        static async Task RunAsync()
{
// 输出当前线程的ID
Console.WriteLine($"异步等待前,当前线程ID:{Thread.CurrentThread.ManagedThreadId}");
// 开始执行异步方法,并等待完成
await Task.Delay();
// 异步等待完成后,再次输出当前线程的ID
Console.WriteLine($"异步等待后,当前线程ID:{Thread.CurrentThread.ManagedThreadId}");
}

异步方法中,调用了Task.Delay方法,这个方法也是可以异步等待的,因此用await关键字来等待50毫秒。

然后,在Main入口处调用以上异步方法。

        static void Main(string[] args)
{
// 声明一个委托实例
Action act = async () =>
{
await RunAsync();
}; // 执行委托
act(); Console.Read();
}

我这里是先声明一个Action委托实例,并通过Lambda表达式调用异步方法,并且异步等待其完成。因为使用了await关键字的方法上必须标注async修饰符,以说明该方法中出现异步等待代码,但是,Main入口方法上是不允许添加async修饰符的,所以,我就用一个委托来调用。

运行这个例子,你会有惊奇发现,请看,有图有真相。

看到没,await等待前,当前的线程是8,异步等待回来后,当前线程就被自动调度到10上了。

            == 在线程8上
await Task.Delay();
== 在线程10上

从代码上看,await前后是连续的,但实际上,在执行阶段,它们已经处于不同的线程上了。

那么,我就想啊,如果在此种情况下使用ThreadLocal变量会发生什么事情。试试看。

        // 线程共享变量
static ThreadLocal<int> local = new ThreadLocal<int>(); static void Main(string[] args)
{
// 声明一个委托实例
Action act = async () =>
{
await RunAsync();
}; // 执行委托
act(); Console.Read();
} static async Task RunAsync()
{
// 给共享变量赋值
local.Value = ;
// 输出变量的值
Console.WriteLine($"异步等待前:{nameof(local)} = {local.Value}");
await Task.Delay(); //异步等待
// 异步等待回来,再次输出变量的值
Console.WriteLine($"异步等待后:{nameof(local)} = {local.Value}");
}

上面例子使用了ThreadLocal声明线程间共享变量,在异步方法中,先给这个变量赋值为53000,然后await开始等待,等待返回后,再次输出变量的值。

好,注意看,意外发生了。

哟,有朋友估计会尖叫了,这是咋回事?await前不是给共享的变量赋了值吗,为什么等待返回后值会变回默认值0呢。前面老周说了,等待前,等待后是有可能处于不同的线程上,而ThreadLocal是为每个线程保存独立的值的。

假设,设置local值为53000是在线程A上执行的,那么,local变量为线程A保留了值53000;当代码执行到await关键字一行后,开始异步等待,而等待返回后,当前代码可能被调度到线程B上了。而53000是为线程A所存储的值,对于线程B,未赋值,所以就得到默认的值0。

很显然,ThreadLocal是不适合在异步上下文中使用的。下面就请出今天的主角——AsyncLocal。

把上面的代码中的ThreadLocal改为AsyncLocal。

        // 线程共享变量
static AsyncLocal<int> local = new AsyncLocal<int>();
……

然后,再运程序,看图。

看到了吧,这下子好了,53000在异步上下文中被保留了。

现在,你明白了AsyncLocal的功能了吧。

本文示例下载地址

【.NET深呼吸】基于异步上下文的本地变量(AsyncLocal)的更多相关文章

  1. 通过transmittable-thread-local源码理解线程池线程本地变量传递的原理

    前提 最近一两个月花了很大的功夫做UCloud服务和中间件迁移到阿里云的工作,没什么空闲时间撸文.想起很早之前写过ThreadLocal的源码分析相关文章,里面提到了ThreadLocal存在一个不能 ...

  2. 线程本地变量ThreadLocal

    一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味 ...

  3. SQL Server-聚焦事务对本地变量、临时表、表变量影响以及日志文件存满时如何收缩(三十一)

    前言 接下来我们将SQL Server基础系列还剩下最后几节内容结束,后续再来讲解SQL Server性能调优,我们开始进入主题. SQL Server事务对本地变量影响 事务对变量影响具体是指什么意 ...

  4. 深入Asyncio(七)异步上下文管理器

    Async Context Managers: async with 在某些场景下(如管理网络资源的连接建立.断开),用支持异步的上下文管理器是很方便的. 那么如何理解async with关键字? 先 ...

  5. Asp.Net Core AsyncLocal 异步上下文

    引子 阅读以下代码,并尝试分析 代码解析 在主线程中,线程Id为1,为线程变量赋值 变量==d6ff 开启一个新的task,此时线程Id为4,变量==d6ff,并调用Task1 开启一个同步Task3 ...

  6. Threadlocal线程本地变量理解

    转载:https://www.cnblogs.com/chengxiao/p/6152824.html 总结: 作用:ThreadLocal 线程本地变量,可用于分布式项目的日志追踪 用法:在切面中生 ...

  7. Java基础加强之多线程篇(线程创建与终止、互斥、通信、本地变量)

    线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...

  8. linux TLS 线程本地变量

    最近在写底层hook的时候, 涉及到线程安全问题, 最开始我设计的时候使用的互斥量, 但是考虑到都是底层函数,加锁会导致性能问题, 一直在思考优化方案, 后来偶然想到,java里面有线程本地变量的AP ...

  9. 线程本地变量ThreadLocal源码解读

      一.ThreadLocal基础知识 原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板 ...

随机推荐

  1. 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

  2. 一个粗心的Bug,JSON格式不规范导致AJAX错误

    一.事件回放  今天工作时碰到了一个奇怪的问题,这个问题很早很早以前也碰到过,不过没想到过这么久了竟然又栽在这里. 当时正在联调一个项目,由于后端没有提供数据接口,于是我直接本地建立了一个 json ...

  3. $.type 怎么精确判断对象类型的 --(源码学习2)

    目标:  var a = [1,2,3];     console.log(typeof a); //->object     console.log($.type(a)); //->ar ...

  4. Android 6.0 权限知识学习笔记

    最近在项目上因为6.0运行时权限吃了亏,发现之前对运行时权限的理解不足,决定回炉重造,重新学习一下Android Permission. 进入正题: Android权限 在Android系统中,权限分 ...

  5. 在jekyll模板博客中添加网易云模块

    最近使用GitHub Pages + Jekyll 搭建了个人博客,作为一名重度音乐患者,博客里面可以不配图,但是不能不配音乐啊. 遂在博客里面引入了网易云模块,这里要感谢网易云的分享机制,对开发者非 ...

  6. 前端MVC学习总结(一)——MVC概要与angular概要、模板与数据绑定

    一.前端MVC概要 1.1.库与框架的区别 框架是一个软件的半成品,在全局范围内给了大的约束.库是工具,在单点上给我们提供功能.框架是依赖库的.AngularJS是框架而jQuery则是库. 1.2. ...

  7. 就这么漂来漂去---一个毕业三个月的java程序员的裸辞风波

    注:这并不是一篇技术文章,而是记录了我这几个月经历的入职,裸辞,找工作的心路历程,简单介绍一个博主的情况,我是16年毕业生,校招进了一家北京的公司,java开发,和很多年轻人一样,干了一段时间,我发现 ...

  8. 关于Visual Studio 未能加载各种Package包的解决方案

    问题: 打开Visual Studio 的时候,总提示未能加载相应的Package包,有时候还无法打开项目,各种提示 解决方案: 进入用户目录 C:\Users\用户名\AppData\Local\M ...

  9. Html.DropDownLis绑定数据库

    效果: 方法一: View: <div class="col-md-md-4"> <div class="input-group"> & ...

  10. RSA算法

    RSA.h #ifndef _RSA_H #define _RSA_H #include<stdio.h> #include<iostream> #include<mat ...