前言

平常写业务代码, 很少会写到多线程. 久了很多东西都忘光光了. 刚好最近在复习 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. 题解:P10732 [NOISG2019 Prelim] Palindromic FizzBuzz

    题解:P10732 [NOISG2019 Prelim] Palindromic FizzBuzz 题意 题意十分明了,给予你一个区间,判断区间中每一个数是否是回文数. 思路 思路比较简单,首先将每一 ...

  2. MFC 关于按键状态获取

    alt键会阻断消息? moousemovealt键无法判断,按下一次 并松开一次状态改变一次#define KeyState GetAsyncKeyState BOOL bCtrlDown = (Ke ...

  3. 一文全解:LVM(逻辑卷管理器)

    前两篇文章已经讲了关于磁盘分区和磁盘阵列的相关内容: 一文全懂:Linux磁盘分区 一文全懂:独立冗余磁盘阵列(RAID) 但是磁盘分区完后再想扩容或者缩容就比较麻烦了,甚至很多时候不能扩容或者缩容, ...

  4. Linux MySQL 服务设置开机自启动

    @ 目录 前言 简介 一.准备工作 二.操作步骤 2.1 启动MySQL服务 2.2 拷贝配置 2.3 赋值权限 2.4 添加为系统服务 2.5 验证 总结 前言 请各大网友尊重本人原创知识分享,谨记 ...

  5. x86_64/aarch64架构下ffpyplayer源码编译

    问题来源: 某鱼上挂着pytorch的aarch64架构下的源码编译,遇到某网友提出的要在aarch64架构下的ubuntu上ffpyplayer源码编译,于是有了本文. ============== ...

  6. CCF A类会议 —— AAAI2022 论文审稿模板

    ======================================================= 前段时间为实验室负责审理AAAI 2022的会议稿件,感觉这个审稿模板还是不错的,这里保 ...

  7. [CEOI2011] Matching 题解

    前言 题目链接:洛谷. 在上一题之后,模拟赛又放了一道 KMP 重定义相等的问题,但是寄了,故再记之. 题意简述 现在给出 \(1 \sim n\) 的排列 \(p\) 和序列 \(h_1, h_2, ...

  8. 用海豚调度器定时调度从Kafka到HDFS的kettle任务脚本

    在实际项目中,从Kafka到HDFS的数据是每天自动生成一个文件,按日期区分.而且Kafka在不断生产数据,因此看看kettle是不是需要时刻运行?能不能按照每日自动生成数据文件? 为了测试实际项目中 ...

  9. Java IO 流详解

    概述 流是一个抽象的概念,代表了数据的无结构化传递.流的本质是数据在不同设备之间的传输.在 Java 中,数据的读取和写入都是以流的方式进行的 在 Java 中,根据数据流向的不同,可以将流分为输入( ...

  10. SMU Summer 2024 Contest Round 8

    SMU Summer 2024 Contest Round 8 Product 思路 注意到 \(\prod\limits_{i=1}^NL_i\le10^5\),也就是说 N 不会超过 16,因为 ...