前言

平常写业务代码, 很少会写到多线程. 久了很多东西都忘光光了. 刚好最近在复习 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. linux 查看crontab任务执行情况

    首先创建一个定时任务,例如: */1 * * * * /usr/bin/curl http://******/admin/Keeperclock/keeper >> /data/wwwro ...

  2. 图的存储、创建、遍历、求最小生成树、最短路径(Java)

    带权无向图 存储结构 存储结构选用邻接表. 当一个图为稀疏图时,使用邻接矩阵法显然要浪费大量的存储空间,而图的邻接表法结合了顺序存储和链式存储方法,大大减少了这种不必要的浪费. 当然,即使我们所处理的 ...

  3. [rCore学习笔记 019]在main中测试本章实现

    写在前面 本随笔是非常菜的菜鸡写的.如有问题请及时提出. 可以联系:1160712160@qq.com GitHhub:https://github.com/WindDevil (目前啥也没有 批处理 ...

  4. Jmeter函数助手19-machineName

    machineName函数用于获取当前计算机的用户名. 存储结果的变量名(可选)

  5. 【Java】 Springboot+Vue 大文件断点续传

    同事在重构老系统的项目时用到了这种大文件上传 第一篇文章是简书的这个: https://www.jianshu.com/p/b59d7dee15a6 是夏大佬写的vue-uploader组件: htt ...

  6. 【Spring】02 过程分析

    回顾JavaWeb三层架构设计: UserDao接口 public interface UserDao { void getUser(); } 实现类 public class UserDaoImpl ...

  7. 【Scala】02 循环

    1.支持集合直接作为循环体的条件: // - - - - 支持集合遍历 - - - - var arr = Array(10, 20, 30) var arr2 = List(10, 20, 30) ...

  8. 【Vue】Re02 指令:第一部分

    一.v-once指令 用于固定一次性赋值,后续Vue实例的赋值更改将不再对v-once指令的元素有效 <!DOCTYPE html> <html lang="en" ...

  9. 【转载】SCI审稿过程中的几种状态

    原文地址: http://cjsphd.blog.163.com/blog/static/44718111201191175154300/ 审稿中涉及到的人: EIC-Editor in Chief ...

  10. 【转载】 GPU地址空间的相关概念

    为了结合上篇 文章   https://www.cnblogs.com/devilmaycry812839668/p/13264080.html 对RTX显卡是否能够实现P2P通信功能,同时专业级别显 ...