前言

平常写业务代码, 很少会写到多线程. 久了很多东西都忘光光了. 刚好最近在复习 RxJS. 有一篇是讲 scheduler 的.

会讲到 JavaScript 异步相关的资讯. 既然如此那就一次过把相关的东西都复习一下呗.

以前写过的文章 : 异步编程 (发布于 2015-04-02)

主要参考

腾飞 – async & await 的前世今生(Updated) (必读)

老赵 – 正确使用异步操作 (必读)

线程基本概念

进程和线程

一个 Application 会用到一个进程和最少一个线程 (不同 App 的进程相互独立, 进程内的线程相互独立)

线程池

创建和销毁线程是很费劲的, 所以通常会有线程池用来缓存线程, 类似 SQL connection pool.

池中控制一定数量的线程, 想用就去池中拿, 用完了就放回去.

为什么要多线程

因为想 multitask. 看过 F1 赛车换轮胎的过程吗? 是不是一堆人一起上? 同样道理, 要快就让多个线程一起上 (当然前提是分工要分的好)

并发和锁

线程多就可能导致并发问题. 线程 1 读了一个值 (static value) 想拿来做逻辑判断, 但同一时间线程 2 写了这个值, 这样就可能导致逻辑错误.

SQL 经常遇到这种事儿. 解决方法就是加锁.

异步编程基本概念

何为异步编程

参考: 知乎 – 认识异步编程

异步对比的是同步. 同步就是程序一行一行的运行. 异步则是并行或者分叉. 而这个分叉不会导致主程序暂停.

它们会一起跑, 主程序可以给一个 callback 让分叉结束后调用, 也可以选择等待分叉结束.

异步编程和多线程的关系

参考 : 一篇文章,搞懂异步和多线程的区别

多线程是实现异步的手段. 但不是唯一手段.

Compute-Bound Operation 和 IO-Bound Operation 与异步的关系

有 2 种常见的异步. 我们常常傻傻分不清楚.

Compute-Bound Operation 指的是那些很花时间的 CPU 操作. 比如 100万次 for loop.

如果只用主程序运行, 那么就会很耗时, 把它们拆成多个线程去完成 (每个线程负责 25万次) 那么时间就快了 4 倍. (当然前提是你有多核 CPU 或者多 CPU)

这是一种异步, 用到了多线程来完成. (F1 赛车的例子)

IO-Bound Operation 指的是那些很花时间的非 CPU 操作, 比如读写 IO, Network 通讯.

如果主程序等待这些操作的话, 那么就很浪费. 所以这里需要一个异步, 当进行 IO 时, 主程序应该释放 CPU 资源, 等待 IO 的 callback.

这是另一种异步, 它需要硬件和 OS 支持的. .NET 有许多 build-in 方法都属于这类异步 e.g. File, SQL, Web Request

下面所有提到的 Task 都是属于第一种异步 (CPU 操作), 因为第二种没什么好说的 .NET build-in 好了, 而且也只能用它 build-in 好的.

创建 Task

早年, 我们是会用 new Thread() 这种直接创建线程的操作的. 但现在我们只用 Task.

一个 main thread, 2 个 subthread.

public class Program
{
public static async Task Main(string[] args)
{
Console.WriteLine("Main Thread Id : " + Environment.CurrentManagedThreadId);
var task1 = Task.Run(() => Action("Task1")); // create and run subthread 1
var task2 = Task.Run(() => Action("Task2")); // create and run subthread 2
for (int index = 0; index < 30; index++)
{
Console.WriteLine($"Main Thread Index : {index}");
}
await Task.WhenAll(task1, task2);
} public static void Action(string taskName)
{
Console.WriteLine($"{taskName} Id: {Environment.CurrentManagedThreadId}");
for (int index = 0; index < 30; index++)
{
Console.WriteLine($"{taskName} Index : {index}");
}
}
}

效果

3 个 thread id 都不相同. 它们是并发执行的. 所以顺序会很乱.

await Task & callback

参考: C#中的Task.WhenAll和Task.WhenAny方法介绍

通常 main thread 最终会等待所有 subthread 完成.

从前 Task 有许多方法 Task.Wait, Task.Result, Task.GetAwaiter().GetResult() 但这些统统不要了, 统一用 await async 就好. 参考: Avoid GetAwaiter().GetResult() at all cost

public static async Task Main(string[] args)
{
var task = Task.Run(Function); // 没有参数就不需要匿名方法
var value = await task;
Console.WriteLine(value); // value
} public static string Function()
{
return "value";
}

等待多个 task 用 Task.WhenAll

await Task.WhenAll(task1, task2);

等待第一个完成的 task 用 Task.WhenAny

var task = await Task.WhenAny(t1, t2, t3);
var value = task.Result;

它返回的是 Task 哦, 但直接 .Result 就可以取值了, 因为它已经执行完了. 其余的 Task 虽然开始执行, 但未必完成了

不想等待的话可以传入 callback 通过 ContinueWith

var task = Task.Run(Function).ContinueWith(t => {
var value = t.Result; // value
});

并发

public class Program
{
public static int index = 0; public static async Task Main(string[] args)
{
await Task.WhenAll(Task.Run(Action), Task.Run(Action));
Console.WriteLine("done");
} public static void Action()
{
var currentIndex = index;
for (var i = 0; i < 100000; i++)
{
index++;
if (index != currentIndex + i + 1)
{
Console.WriteLine("concurreny");
break;
}
}
}
}

2 个 task 都读写 static index 于是问题就出现了. 解决方法是加锁.

public class Program
{
private static object _lock = new object();
public static void Action()
{
lock (_lock) {
for (var i = 0; i < 100000; i++)
{
...
}
}
}
}

ConfigureAwait

参考:

A deep dive into ConfigureAwait

Stack Overflow – ConfigureAwait(false) relevant in ASP.NET Core?

configureAwait 声明异步执行完是否用回之前的 context, 绝大部分情况下是不需要的, 所以 set false. 它可以提升性能, 减少 deadlock 等等好处.

但是 .NET Framework default 是 true, 所以早年经常需要写 configureAwait(false)

幸好 .NET Core 已经修改了 async 的实现. 它的效果相当于 configureAwait(false). 所以我们也不再需要理会它了.

总结

大部分时候, 我们写 await 都是 for IO 异步(e.g. File, SQL, Network). 这种情况是不会增加线程的. 相反在等待期间它会把主线程让出去给其它人用.

只有需要大量 CPU 运算的情况下, 我们才需要创建更多线程去分工处理.

ASP.NET Core – Thread, Task, Async 线程与异步编程的更多相关文章

  1. C# ASP.NET Core使用HttpClient的同步和异步请求

    引用 Newtonsoft.Json // Post请求 public string PostResponse(string url,string postData,out string status ...

  2. ASP.NET Core 登录登出 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 登录登出 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 登录登出 上一章节我们总算完善了注册的功能,而且也添加了一个用户,现 ...

  3. ASP.NET Core 新增用户 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 新增用户 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 新增用户 上一章节我们实现了一个注册表单,但也留了一些东西还没完成, ...

  4. ASP.NET Core 路由 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 路由 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 路由 前两章节中,我们提到 ASP.NET Core 支持 MVC 开发 ...

  5. ASP.NET Core 配置 MVC - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 配置 MVC - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 配置 MVC 前面几章节中,我们都是基于 ASP.NET 空项目 ...

  6. ASP.NET Core 静态文件 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 静态文件 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 静态文件 前几章节中,我们学习了 ASP.NET Core 的中间件 ...

  7. ASP.NET Core 异常和错误处理 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 异常和错误处理 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 异常和错误处理 上一章节中,我们学习了 ASP.NET Cor ...

  8. ASP.NET Core 中间件 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 中间件 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 中间件 上一章节中,我们我们有讲到 Startup 类中的 Confi ...

  9. ASP.NET Core 项目配置 ( Startup ) - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 项目配置 ( Startup ) - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 项目配置 ( Startup ) 前面几章节 ...

  10. ASP.NET Core 基础教程总结 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 基础教程总结 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 基础教程总结 ASP.NET Core 基础教程总算是有了个简单 ...

随机推荐

  1. 怒肝半月!Python 学习路线+资源大汇总

    Python 学习路线 by 鱼皮. 原创不易,请勿抄袭,违者必究! 大家好,我是鱼皮,肝了十天左右的 Python 学习路线终于来了~ 和之前一样,在看路线前,建议大家先通过以下视频了解几个问题: ...

  2. linux mysql 允许进行远程连接 比如 navicat

    出于安全方面考虑默认只允许本机(localhost, 127.0.0.1)来连接访问.所以开启远程访问权限.登录mysqlmysql -uroot -pxxxxxx 1:GRANT ALL PRIVI ...

  3. Pycharm中开发vue element项目时eslint的安装和使用

    在PyCharm中使用ESLint对Element UI进行语法检查和代码风格检查的配置步骤如下: 确保你的项目已经配置了ESLint并且可以正常运行.如果尚未安装ESLint,请先使用npm(或者你 ...

  4. [oeasy]python0115_西里尔字符集_Cyrillic_俄文字符编码_KOI_8859系列

    各语言字符编码 回忆上次内容 上次回顾了 非ascii的拉丁字符编码的进化过程 0-127 是 ascii 的领域   西欧.北欧语言 大多使用 拉丁字符 由iso组织 制定iso-8859-1   ...

  5. C#全局键盘监听(Hook)的使用

    一.为什么需要全局键盘监听? 在某些情况下应用程序需要实现快捷键执行特定功能,例如大家熟知的QQ截图功能Ctrl+Alt+A快捷键,只要QQ程序在运行(无论是拥有焦点还是处于后台运行状态),都可以按下 ...

  6. vue小知识~eventBus

    eventBus是指在向全区暴露这个vue对象,此时在任意一个地方都可以使用vue相关的实例 在main.js配置 Vue.prototype.$bus=new Vue() 此时整个应用都可以使用vu ...

  7. 搭建lnmp环境-nginx(第一步)

    建议: 本次lnmp采用yum形式安装,编译安装过于繁琐,操作不好还不如yum安装,所以不推荐. 全部安装在宿主机上,如果需要安装多个版本的软件才使用docker nginx无所谓版本了 刚安装好系统 ...

  8. RDD入门了解

    RDD即resilient distributed dataset 弹性分布式数据集,简单来说就是数据集,可以类比python的list dict:但是数据是分布式存储的,可用于分布式计算:可以存在内 ...

  9. 【MySQL】下发功能SQL

    SQL参考文章: https://www.jb51.net/article/15627.htm 下发,就是从别的表中同步数据到此表中,也可能是来自不同库的表,或者不同实例的表 下发的逻辑要求:如果没有 ...

  10. XML 教程——检视阅读

    基本 XML 简介 XML 指可扩展标记语言(eXtensible Markup Language). XML 被设计用来传输和存储数据.HTML 被设计用来显示数据. 什么是 XML? XML 指可 ...