[{"data":1,"prerenderedAt":6386},["ShallowReactive",2],{"page-\u002Fcsharp\u002Fc的多线程":3},{"id":4,"title":5,"body":6,"description":150,"extension":6380,"meta":6381,"navigation":516,"path":6382,"seo":6383,"stem":6384,"__hash__":6385},"content\u002Fcsharp\u002FC#的多线程.md","C#的多线程",{"type":7,"value":8,"toc":6363},"minimark",[9,13,30,33,141,144,210,221,285,288,292,295,488,495,533,544,662,665,668,740,743,809,816,951,954,965,1127,1130,1159,1170,1181,1223,1226,1372,1375,1391,1476,1479,1482,1550,1553,1714,1717,1838,1844,1873,1877,1893,1896,2665,2669,2672,2898,2901,2966,2969,2979,2982,3076,3079,3082,3085,3088,3091,3094,3097,3100,3103,3108,3111,3179,3182,3185,3188,3191,3239,3294,3297,3300,3424,3427,3430,3438,3441,3537,3540,3597,3600,3603,3606,3609,3613,3624,3792,3795,3798,3918,3921,4044,4115,4119,4122,4328,4331,4529,4532,4703,4706,4709,4712,4716,4719,4911,4914,5029,5032,5042,5053,5151,5155,5168,5181,5188,5201,5429,5432,5579,5582,5586,5608,5611,5623,5626,5629,6217,6220,6356,6359],[10,11,12],"h2",{"id":12},"基础知识",[14,15,16,17,21,22,25,26,29],"p",{},"线程上下文：是操作系统内核为每个线程维护的一个数据结构，记录了线程",[18,19,20],"strong",{},"暂停执行时","的完整状态。当",[18,23,24],{},"操作系统决定切换正在运行的线程时","，它必须保存当前线程的上下文，并加载下一个线程的上下文——这个过程称为",[18,27,28],{},"上下文切换（Context Switch）","。",[14,31,32],{},"包含内容：",[34,35,36,52],"table",{},[37,38,39],"thead",{},[40,41,42,46,49],"tr",{},[43,44,45],"th",{},"类别",[43,47,48],{},"内容",[43,50,51],{},"作用",[53,54,55,69,89,106,119,132],"tbody",{},[40,56,57,63,66],{},[58,59,60],"td",{},[18,61,62],{},"CPU 寄存器",[58,64,65],{},"程序计数器（PC）、栈指针（SP）、通用寄存器等",[58,67,68],{},"决定线程“从哪里继续执行”",[40,70,71,76,82],{},[58,72,73],{},[18,74,75],{},"内核栈",[58,77,78,79],{},"线程在内核态执行时的",[18,80,81],{},"调用栈",[58,83,84,85,88],{},"存储系统调用、",[18,86,87],{},"中断处理","的信息",[40,90,91,96,103],{},[58,92,93],{},[18,94,95],{},"线程环境块（TEB）",[58,97,98,99,102],{},"线程",[18,100,101],{},"局部存储","（TLS）、异常处理链等",[58,104,105],{},"记录线程特有的数据",[40,107,108,113,116],{},[58,109,110],{},[18,111,112],{},"安全上下文",[58,114,115],{},"访问令牌、权限信息",[58,117,118],{},"决定线程能访问哪些资源",[40,120,121,126,129],{},[58,122,123],{},[18,124,125],{},"浮点\u002F向量寄存器",[58,127,128],{},"SSE、AVX 等寄存器状态",[58,130,131],{},"支持浮点运算和 SIMD 指令",[40,133,134,137,139],{},[58,135,136],{},"线程上下文是操作系统级别的，每个线程都有自己的上下文。",[58,138],{},[58,140],{},[14,142,143],{},"上下文切换发生在时间片耗尽、阻塞、优先级抢占、显式让出等情况。",[145,146,151],"pre",{"className":147,"code":148,"language":149,"meta":150,"style":150},"language-mermaid shiki shiki-themes github-light github-dark","sequenceDiagram\n    participant App as 用户态线程 A\n    participant Kernel as 操作系统内核\n    participant App2 as 用户态线程 B\n    App->>Kernel: 时间片用完 \u002F 等待I\u002FO \u002F 主动切换\n    Kernel->>Kernel: 保存线程A的上下文\u003Cbr>（寄存器、PC、栈等）\n    Kernel->>Kernel: 选择下一个线程B\n    Kernel->>Kernel: 加载线程B的上下文\n    Kernel->>App2: 恢复执行（从上次中断点继续）\n","mermaid","",[152,153,154,162,168,174,180,186,192,198,204],"code",{"__ignoreMap":150},[155,156,159],"span",{"class":157,"line":158},"line",1,[155,160,161],{},"sequenceDiagram\n",[155,163,165],{"class":157,"line":164},2,[155,166,167],{},"    participant App as 用户态线程 A\n",[155,169,171],{"class":157,"line":170},3,[155,172,173],{},"    participant Kernel as 操作系统内核\n",[155,175,177],{"class":157,"line":176},4,[155,178,179],{},"    participant App2 as 用户态线程 B\n",[155,181,183],{"class":157,"line":182},5,[155,184,185],{},"    App->>Kernel: 时间片用完 \u002F 等待I\u002FO \u002F 主动切换\n",[155,187,189],{"class":157,"line":188},6,[155,190,191],{},"    Kernel->>Kernel: 保存线程A的上下文\u003Cbr>（寄存器、PC、栈等）\n",[155,193,195],{"class":157,"line":194},7,[155,196,197],{},"    Kernel->>Kernel: 选择下一个线程B\n",[155,199,201],{"class":157,"line":200},8,[155,202,203],{},"    Kernel->>Kernel: 加载线程B的上下文\n",[155,205,207],{"class":157,"line":206},9,[155,208,209],{},"    Kernel->>App2: 恢复执行（从上次中断点继续）\n",[14,211,212,213,216,217,220],{},"在Windows上，一次线程上下文切换需要 ",[18,214,215],{},"几百纳秒到几微秒","，但如果",[18,218,219],{},"涉及跨进程切换","，开销会更大。其代价主要来自：",[34,222,223,233],{},[37,224,225],{},[40,226,227,230],{},[43,228,229],{},"开销来源",[43,231,232],{},"说明",[53,234,235,245,255,265,275],{},[40,236,237,242],{},[58,238,239],{},[18,240,241],{},"模式切换",[58,243,244],{},"从用户态切换到内核态（反之亦然），涉及权限检查和栈切换",[40,246,247,252],{},[58,248,249],{},[18,250,251],{},"寄存器保存\u002F恢复",[58,253,254],{},"需要保存\u002F加载几十个寄存器的值",[40,256,257,262],{},[58,258,259],{},[18,260,261],{},"TLB 刷新",[58,263,264],{},"切换到不同进程时，CPU 的地址转换缓存（TLB）会失效，导致后续内存访问变慢",[40,266,267,272],{},[58,268,269],{},[18,270,271],{},"CPU 缓存污染",[58,273,274],{},"新线程的代码和数据会挤掉旧线程的缓存内容",[40,276,277,282],{},[58,278,279],{},[18,280,281],{},"调度器决策",[58,283,284],{},"操作系统需要运行调度算法决定下一个运行哪个线程",[10,286,287],{"id":287},"代码相关",[289,290,291],"h3",{"id":291},"原子操作和锁",[14,293,294],{},"硬件层面的线程和软件层面的线程：",[145,296,298],{"className":147,"code":297,"language":149,"meta":150,"style":150},"graph TB\n    subgraph Software [软件层]\n        KT1[内核线程 A]\n        KT2[内核线程 B]\n        KT3[内核线程 C]\n        KT4[内核线程 D]\n    end\n    \n    subgraph Scheduler [操作系统调度器]\n        S[调度决策\u003Cbr>决定哪个线程\u003Cbr>运行在哪个逻辑核心]\n    end\n    \n    subgraph Hardware [硬件层]\n        subgraph Core1 [物理核心 1]\n            HT1[逻辑核心 1\u003Cbr>硬件线程 1]\n            HT2[逻辑核心 2\u003Cbr>硬件线程 2]\n        end\n        subgraph Core2 [物理核心 2]\n            HT3[逻辑核心 3\u003Cbr>硬件线程 3]\n            HT4[逻辑核心 4\u003Cbr>硬件线程 4]\n        end\n    end\n    \n    KT1 --> S\n    KT2 --> S\n    KT3 --> S\n    KT4 --> S\n    \n    S -->|分配| HT1\n    S -->|分配| HT2\n    S -->|分配| HT3\n    S -->|分配| HT4\n    \n    Note1[操作系统决定哪个内核线程\u003Cbr>运行在哪个硬件线程上\u003Cbr>硬件只是被动执行]\n",[152,299,300,305,310,315,320,325,330,335,340,345,351,356,361,367,373,379,385,391,397,403,409,414,419,424,430,436,442,448,453,459,465,471,477,482],{"__ignoreMap":150},[155,301,302],{"class":157,"line":158},[155,303,304],{},"graph TB\n",[155,306,307],{"class":157,"line":164},[155,308,309],{},"    subgraph Software [软件层]\n",[155,311,312],{"class":157,"line":170},[155,313,314],{},"        KT1[内核线程 A]\n",[155,316,317],{"class":157,"line":176},[155,318,319],{},"        KT2[内核线程 B]\n",[155,321,322],{"class":157,"line":182},[155,323,324],{},"        KT3[内核线程 C]\n",[155,326,327],{"class":157,"line":188},[155,328,329],{},"        KT4[内核线程 D]\n",[155,331,332],{"class":157,"line":194},[155,333,334],{},"    end\n",[155,336,337],{"class":157,"line":200},[155,338,339],{},"    \n",[155,341,342],{"class":157,"line":206},[155,343,344],{},"    subgraph Scheduler [操作系统调度器]\n",[155,346,348],{"class":157,"line":347},10,[155,349,350],{},"        S[调度决策\u003Cbr>决定哪个线程\u003Cbr>运行在哪个逻辑核心]\n",[155,352,354],{"class":157,"line":353},11,[155,355,334],{},[155,357,359],{"class":157,"line":358},12,[155,360,339],{},[155,362,364],{"class":157,"line":363},13,[155,365,366],{},"    subgraph Hardware [硬件层]\n",[155,368,370],{"class":157,"line":369},14,[155,371,372],{},"        subgraph Core1 [物理核心 1]\n",[155,374,376],{"class":157,"line":375},15,[155,377,378],{},"            HT1[逻辑核心 1\u003Cbr>硬件线程 1]\n",[155,380,382],{"class":157,"line":381},16,[155,383,384],{},"            HT2[逻辑核心 2\u003Cbr>硬件线程 2]\n",[155,386,388],{"class":157,"line":387},17,[155,389,390],{},"        end\n",[155,392,394],{"class":157,"line":393},18,[155,395,396],{},"        subgraph Core2 [物理核心 2]\n",[155,398,400],{"class":157,"line":399},19,[155,401,402],{},"            HT3[逻辑核心 3\u003Cbr>硬件线程 3]\n",[155,404,406],{"class":157,"line":405},20,[155,407,408],{},"            HT4[逻辑核心 4\u003Cbr>硬件线程 4]\n",[155,410,412],{"class":157,"line":411},21,[155,413,390],{},[155,415,417],{"class":157,"line":416},22,[155,418,334],{},[155,420,422],{"class":157,"line":421},23,[155,423,339],{},[155,425,427],{"class":157,"line":426},24,[155,428,429],{},"    KT1 --> S\n",[155,431,433],{"class":157,"line":432},25,[155,434,435],{},"    KT2 --> S\n",[155,437,439],{"class":157,"line":438},26,[155,440,441],{},"    KT3 --> S\n",[155,443,445],{"class":157,"line":444},27,[155,446,447],{},"    KT4 --> S\n",[155,449,451],{"class":157,"line":450},28,[155,452,339],{},[155,454,456],{"class":157,"line":455},29,[155,457,458],{},"    S -->|分配| HT1\n",[155,460,462],{"class":157,"line":461},30,[155,463,464],{},"    S -->|分配| HT2\n",[155,466,468],{"class":157,"line":467},31,[155,469,470],{},"    S -->|分配| HT3\n",[155,472,474],{"class":157,"line":473},32,[155,475,476],{},"    S -->|分配| HT4\n",[155,478,480],{"class":157,"line":479},33,[155,481,339],{},[155,483,485],{"class":157,"line":484},34,[155,486,487],{},"    Note1[操作系统决定哪个内核线程\u003Cbr>运行在哪个硬件线程上\u003Cbr>硬件只是被动执行]\n",[14,489,490,491,494],{},"CPU亲和性：是指",[18,492,493],{},"线程是否倾向于（或被强制）在同一个 CPU 核心上反复运行","，这个主要是为了更加合理的使用CPU的多级缓存性质。每个核心有自己的 L1\u002FL2 缓存：",[145,496,500],{"className":497,"code":498,"language":499,"meta":150,"style":150},"language-c shiki shiki-themes github-light github-dark","\u002F\u002F 当线程在核心1上运行时\n线程访问数据 → 数据加载到核心1的 L1\u002FL2 缓存\n\n\u002F\u002F 如果线程切换到核心2\n数据不在核心2的缓存中 → 缓存未命中（Cache Miss）\n需要从 L3 或内存重新加载 → 延迟增加 10-100 倍\n","c",[152,501,502,507,512,518,523,528],{"__ignoreMap":150},[155,503,504],{"class":157,"line":158},[155,505,506],{},"\u002F\u002F 当线程在核心1上运行时\n",[155,508,509],{"class":157,"line":164},[155,510,511],{},"线程访问数据 → 数据加载到核心1的 L1\u002FL2 缓存\n",[155,513,514],{"class":157,"line":170},[155,515,517],{"emptyLinePlaceholder":516},true,"\n",[155,519,520],{"class":157,"line":176},[155,521,522],{},"\u002F\u002F 如果线程切换到核心2\n",[155,524,525],{"class":157,"line":182},[155,526,527],{},"数据不在核心2的缓存中 → 缓存未命中（Cache Miss）\n",[155,529,530],{"class":157,"line":188},[155,531,532],{},"需要从 L3 或内存重新加载 → 延迟增加 10-100 倍\n",[14,534,535,536,539,540,543],{},"线程频繁迁移会导致",[18,537,538],{},"缓存未命中率飙升","，",[18,541,542],{},"那些被高频访问、并且访问模式呈现强局部性的数据","，从亲和性中获益最大。如果一个数据只被一个线程给访问的话，可以完全驻留在该核心的 L1\u002FL2 缓存中。",[145,545,549],{"className":546,"code":547,"language":548,"meta":150,"style":150},"language-cs shiki shiki-themes github-light github-dark","\u002F\u002F 场景：每个线程维护自己的计数器\nclass PerThreadData\n{\n    \u002F\u002F 线程局部存储（TLS）\n    [ThreadStatic]\n    private static long _requestCount;\n    \n    [ThreadStatic]\n    private static long _cacheHitCount;\n    \n    public static void ProcessRequest()\n    {\n        \u002F\u002F 这些变量完全驻留在当前核心的缓存中\n        \u002F\u002F 绑定核心后，L1\u002FL2 命中率接近 100%\n        _requestCount++;\n        \n        if (IsCacheHit())\n            _cacheHitCount++;\n        \n        \u002F\u002F 计算命中率\n        double hitRate = (double)_cacheHitCount \u002F _requestCount;\n    }\n}\n","cs",[152,550,551,556,561,566,571,576,581,585,589,594,598,603,608,613,618,623,628,633,638,642,647,652,657],{"__ignoreMap":150},[155,552,553],{"class":157,"line":158},[155,554,555],{},"\u002F\u002F 场景：每个线程维护自己的计数器\n",[155,557,558],{"class":157,"line":164},[155,559,560],{},"class PerThreadData\n",[155,562,563],{"class":157,"line":170},[155,564,565],{},"{\n",[155,567,568],{"class":157,"line":176},[155,569,570],{},"    \u002F\u002F 线程局部存储（TLS）\n",[155,572,573],{"class":157,"line":182},[155,574,575],{},"    [ThreadStatic]\n",[155,577,578],{"class":157,"line":188},[155,579,580],{},"    private static long _requestCount;\n",[155,582,583],{"class":157,"line":194},[155,584,339],{},[155,586,587],{"class":157,"line":200},[155,588,575],{},[155,590,591],{"class":157,"line":206},[155,592,593],{},"    private static long _cacheHitCount;\n",[155,595,596],{"class":157,"line":347},[155,597,339],{},[155,599,600],{"class":157,"line":353},[155,601,602],{},"    public static void ProcessRequest()\n",[155,604,605],{"class":157,"line":358},[155,606,607],{},"    {\n",[155,609,610],{"class":157,"line":363},[155,611,612],{},"        \u002F\u002F 这些变量完全驻留在当前核心的缓存中\n",[155,614,615],{"class":157,"line":369},[155,616,617],{},"        \u002F\u002F 绑定核心后，L1\u002FL2 命中率接近 100%\n",[155,619,620],{"class":157,"line":375},[155,621,622],{},"        _requestCount++;\n",[155,624,625],{"class":157,"line":381},[155,626,627],{},"        \n",[155,629,630],{"class":157,"line":387},[155,631,632],{},"        if (IsCacheHit())\n",[155,634,635],{"class":157,"line":393},[155,636,637],{},"            _cacheHitCount++;\n",[155,639,640],{"class":157,"line":399},[155,641,627],{},[155,643,644],{"class":157,"line":405},[155,645,646],{},"        \u002F\u002F 计算命中率\n",[155,648,649],{"class":157,"line":411},[155,650,651],{},"        double hitRate = (double)_cacheHitCount \u002F _requestCount;\n",[155,653,654],{"class":157,"line":416},[155,655,656],{},"    }\n",[155,658,659],{"class":157,"line":421},[155,660,661],{},"}\n",[14,663,664],{},"这种模式我们可以从其他的应用中得到启发，典型的就是Redis6.0之前，虽然它是单线程，但是它的性能很强，主要就是因为redis使用饿了单线程的事件循环，核心数据完全绑定在一个核心上，获得了极高的缓存命中率。多线程版本虽然提高了吞吐，但引入了更多的缓存一致性开销。",[14,666,667],{},"这里是判断是否需要亲和性的方法：",[34,669,670,683],{},[37,671,672],{},[40,673,674,677,680],{},[43,675,676],{},"问题",[43,678,679],{},"是",[43,681,682],{},"否",[53,684,685,696,707,718,729],{},[40,686,687,690,693],{},[58,688,689],{},"数据被单一线程独占访问？",[58,691,692],{},"✅ 强烈建议",[58,694,695],{},"考虑 Per-CPU 拆分",[40,697,698,701,704],{},[58,699,700],{},"访问频率 > 10万次\u002F秒？",[58,702,703],{},"✅ 建议",[58,705,706],{},"收益不明显",[40,708,709,712,715],{},[58,710,711],{},"数据大小 \u003C L2 缓存（256KB）？",[58,713,714],{},"✅ 可完全缓存",[58,716,717],{},"可能溢出到 L3",[40,719,720,723,726],{},[58,721,722],{},"多线程高频写入同一缓存行？",[58,724,725],{},"⚠️ 应避免",[58,727,728],{},"考虑缓存行对齐",[40,730,731,734,737],{},[58,732,733],{},"只是偶尔读取的配置数据？",[58,735,736],{},"❌ 不需要",[58,738,739],{},"无收益",[14,741,742],{},"我们在.NET中可以设置线程的亲和性：",[145,744,746],{"className":546,"code":745,"language":548,"meta":150,"style":150},"using System.Diagnostics;\nusing System.Runtime.InteropServices;\n\n\u002F\u002F Windows 上的线程亲和性设置\npublic static void SetThreadAffinity(int coreIndex)\n{\n    var process = Process.GetCurrentProcess();\n    var thread = Process.GetCurrentProcess().Threads[0];\n    \n    \u002F\u002F 设置线程的亲和性掩码（第 coreIndex 位为 1）\n    var affinityMask = (IntPtr)(1L \u003C\u003C coreIndex);\n    thread.ProcessorAffinity = affinityMask;\n}\n",[152,747,748,753,758,762,767,772,776,781,786,790,795,800,805],{"__ignoreMap":150},[155,749,750],{"class":157,"line":158},[155,751,752],{},"using System.Diagnostics;\n",[155,754,755],{"class":157,"line":164},[155,756,757],{},"using System.Runtime.InteropServices;\n",[155,759,760],{"class":157,"line":170},[155,761,517],{"emptyLinePlaceholder":516},[155,763,764],{"class":157,"line":176},[155,765,766],{},"\u002F\u002F Windows 上的线程亲和性设置\n",[155,768,769],{"class":157,"line":182},[155,770,771],{},"public static void SetThreadAffinity(int coreIndex)\n",[155,773,774],{"class":157,"line":188},[155,775,565],{},[155,777,778],{"class":157,"line":194},[155,779,780],{},"    var process = Process.GetCurrentProcess();\n",[155,782,783],{"class":157,"line":200},[155,784,785],{},"    var thread = Process.GetCurrentProcess().Threads[0];\n",[155,787,788],{"class":157,"line":206},[155,789,339],{},[155,791,792],{"class":157,"line":347},[155,793,794],{},"    \u002F\u002F 设置线程的亲和性掩码（第 coreIndex 位为 1）\n",[155,796,797],{"class":157,"line":353},[155,798,799],{},"    var affinityMask = (IntPtr)(1L \u003C\u003C coreIndex);\n",[155,801,802],{"class":157,"line":358},[155,803,804],{},"    thread.ProcessorAffinity = affinityMask;\n",[155,806,807],{"class":157,"line":363},[155,808,661],{},[14,810,811,812,815],{},"我们需要了解锁在系统内核中的原生形态，常见的锁以及锁不可用的时候，等待线程的行为模式。\n互斥锁的实现是 ",[18,813,814],{},"线程阻塞和唤醒"," ：",[145,817,819],{"className":147,"code":818,"language":149,"meta":150,"style":150},"graph TB\n    subgraph Userspace [用户态]\n        A[线程尝试获取锁]\n        B{锁可用?}\n        C[进入临界区]\n        D[锁被占用]\n    end\n    \n    subgraph Kernel [内核态]\n        E[线程加入等待队列]\n        F[调度器挂起线程]\n        G[上下文切换]\n        H[锁释放]\n        I[唤醒等待队列头部线程]\n        J[重新调度]\n    end\n    \n    A --> B\n    B -->|是| C\n    B -->|否| D\n    D --> E\n    E --> F\n    F --> G\n    G -->|等待| H\n    H --> I\n    I --> J\n    J -->|重新尝试| A\n",[152,820,821,825,830,835,840,845,850,854,858,863,868,873,878,883,888,893,897,901,906,911,916,921,926,931,936,941,946],{"__ignoreMap":150},[155,822,823],{"class":157,"line":158},[155,824,304],{},[155,826,827],{"class":157,"line":164},[155,828,829],{},"    subgraph Userspace [用户态]\n",[155,831,832],{"class":157,"line":170},[155,833,834],{},"        A[线程尝试获取锁]\n",[155,836,837],{"class":157,"line":176},[155,838,839],{},"        B{锁可用?}\n",[155,841,842],{"class":157,"line":182},[155,843,844],{},"        C[进入临界区]\n",[155,846,847],{"class":157,"line":188},[155,848,849],{},"        D[锁被占用]\n",[155,851,852],{"class":157,"line":194},[155,853,334],{},[155,855,856],{"class":157,"line":200},[155,857,339],{},[155,859,860],{"class":157,"line":206},[155,861,862],{},"    subgraph Kernel [内核态]\n",[155,864,865],{"class":157,"line":347},[155,866,867],{},"        E[线程加入等待队列]\n",[155,869,870],{"class":157,"line":353},[155,871,872],{},"        F[调度器挂起线程]\n",[155,874,875],{"class":157,"line":358},[155,876,877],{},"        G[上下文切换]\n",[155,879,880],{"class":157,"line":363},[155,881,882],{},"        H[锁释放]\n",[155,884,885],{"class":157,"line":369},[155,886,887],{},"        I[唤醒等待队列头部线程]\n",[155,889,890],{"class":157,"line":375},[155,891,892],{},"        J[重新调度]\n",[155,894,895],{"class":157,"line":381},[155,896,334],{},[155,898,899],{"class":157,"line":387},[155,900,339],{},[155,902,903],{"class":157,"line":393},[155,904,905],{},"    A --> B\n",[155,907,908],{"class":157,"line":399},[155,909,910],{},"    B -->|是| C\n",[155,912,913],{"class":157,"line":405},[155,914,915],{},"    B -->|否| D\n",[155,917,918],{"class":157,"line":411},[155,919,920],{},"    D --> E\n",[155,922,923],{"class":157,"line":416},[155,924,925],{},"    E --> F\n",[155,927,928],{"class":157,"line":421},[155,929,930],{},"    F --> G\n",[155,932,933],{"class":157,"line":426},[155,934,935],{},"    G -->|等待| H\n",[155,937,938],{"class":157,"line":432},[155,939,940],{},"    H --> I\n",[155,942,943],{"class":157,"line":438},[155,944,945],{},"    I --> J\n",[155,947,948],{"class":157,"line":444},[155,949,950],{},"    J -->|重新尝试| A\n",[14,952,953],{},"因此一个核心如果其中的线程在运行的时候，进不去临界区，就会进入休眠，然后内核调用上下文切换进执行其他线程的任务，这个是比较基础的单核CPU的模型，这个调度的目的也是为了防止CPU空转。",[14,955,956,957,960,961,964],{},"自旋锁是一种线程同步机制，当线程",[18,958,959],{},"尝试获取已被占用的锁时","，它不会进入休眠状态，而是在一个循环中",[18,962,963],{},"不断检查","锁是否可用——这个过程称为\"自旋\"。\n和互斥不同，互斥如果锁不可用就会去进入等待队列，但是自旋如果不可用就会循环检查锁状态。但最重要的是，自旋的时候会一直消耗着CPU，会一直保持着用户态，不会出现陷入内核态的情况。因此没有用户态切换延迟！",[145,966,968],{"className":546,"code":967,"language":548,"meta":150,"style":150},"public struct SimpleSpinLock\n{\n    private int _lock;  \u002F\u002F 0 = 空闲, 1 = 占用\n    \n    public void Enter()\n    {\n        \u002F\u002F 自旋：不断尝试获取锁\n        while (Interlocked.CompareExchange(ref _lock, 1, 0) != 0)\n        {\n            \u002F\u002F 锁被占用，继续循环等待\n            \u002F\u002F 可选：短暂停顿，避免过度消耗CPU\n            Thread.SpinWait(10);\n        }\n    }\n    \n    public void Exit()\n    {\n        \u002F\u002F 原子释放锁\n        Interlocked.Exchange(ref _lock, 0);\n    }\n}\n\n\u002F\u002F 使用示例\nSimpleSpinLock spinLock = new SimpleSpinLock();\nspinLock.Enter();\ntry\n{\n    \u002F\u002F 临界区代码（必须极短）\n    counter++;\n}\nfinally\n{\n    spinLock.Exit();\n}\n",[152,969,970,975,979,984,988,993,997,1002,1007,1012,1017,1022,1027,1032,1036,1040,1045,1049,1054,1059,1063,1067,1071,1076,1081,1086,1091,1095,1100,1105,1109,1114,1118,1123],{"__ignoreMap":150},[155,971,972],{"class":157,"line":158},[155,973,974],{},"public struct SimpleSpinLock\n",[155,976,977],{"class":157,"line":164},[155,978,565],{},[155,980,981],{"class":157,"line":170},[155,982,983],{},"    private int _lock;  \u002F\u002F 0 = 空闲, 1 = 占用\n",[155,985,986],{"class":157,"line":176},[155,987,339],{},[155,989,990],{"class":157,"line":182},[155,991,992],{},"    public void Enter()\n",[155,994,995],{"class":157,"line":188},[155,996,607],{},[155,998,999],{"class":157,"line":194},[155,1000,1001],{},"        \u002F\u002F 自旋：不断尝试获取锁\n",[155,1003,1004],{"class":157,"line":200},[155,1005,1006],{},"        while (Interlocked.CompareExchange(ref _lock, 1, 0) != 0)\n",[155,1008,1009],{"class":157,"line":206},[155,1010,1011],{},"        {\n",[155,1013,1014],{"class":157,"line":347},[155,1015,1016],{},"            \u002F\u002F 锁被占用，继续循环等待\n",[155,1018,1019],{"class":157,"line":353},[155,1020,1021],{},"            \u002F\u002F 可选：短暂停顿，避免过度消耗CPU\n",[155,1023,1024],{"class":157,"line":358},[155,1025,1026],{},"            Thread.SpinWait(10);\n",[155,1028,1029],{"class":157,"line":363},[155,1030,1031],{},"        }\n",[155,1033,1034],{"class":157,"line":369},[155,1035,656],{},[155,1037,1038],{"class":157,"line":375},[155,1039,339],{},[155,1041,1042],{"class":157,"line":381},[155,1043,1044],{},"    public void Exit()\n",[155,1046,1047],{"class":157,"line":387},[155,1048,607],{},[155,1050,1051],{"class":157,"line":393},[155,1052,1053],{},"        \u002F\u002F 原子释放锁\n",[155,1055,1056],{"class":157,"line":399},[155,1057,1058],{},"        Interlocked.Exchange(ref _lock, 0);\n",[155,1060,1061],{"class":157,"line":405},[155,1062,656],{},[155,1064,1065],{"class":157,"line":411},[155,1066,661],{},[155,1068,1069],{"class":157,"line":416},[155,1070,517],{"emptyLinePlaceholder":516},[155,1072,1073],{"class":157,"line":421},[155,1074,1075],{},"\u002F\u002F 使用示例\n",[155,1077,1078],{"class":157,"line":426},[155,1079,1080],{},"SimpleSpinLock spinLock = new SimpleSpinLock();\n",[155,1082,1083],{"class":157,"line":432},[155,1084,1085],{},"spinLock.Enter();\n",[155,1087,1088],{"class":157,"line":438},[155,1089,1090],{},"try\n",[155,1092,1093],{"class":157,"line":444},[155,1094,565],{},[155,1096,1097],{"class":157,"line":450},[155,1098,1099],{},"    \u002F\u002F 临界区代码（必须极短）\n",[155,1101,1102],{"class":157,"line":455},[155,1103,1104],{},"    counter++;\n",[155,1106,1107],{"class":157,"line":461},[155,1108,661],{},[155,1110,1111],{"class":157,"line":467},[155,1112,1113],{},"finally\n",[155,1115,1116],{"class":157,"line":473},[155,1117,565],{},[155,1119,1120],{"class":157,"line":479},[155,1121,1122],{},"    spinLock.Exit();\n",[155,1124,1125],{"class":157,"line":484},[155,1126,661],{},[14,1128,1129],{},"我们知道C#提供了移交叫做Interlocked的类，他可以帮助我们实现cpu的原子操作。",[1131,1132,1133],"blockquote",{},[14,1134,1135,1138,1139,1142,1143,1146,1147,1150,1151,1154,1155,1158],{},[155,1136,1137],{},"!tip"," 💡 ",[18,1140,1141],{},"对比","：",[152,1144,1145],{},"Interlocked.Increment"," 使用的 ",[152,1148,1149],{},"lock"," 前缀指令",[18,1152,1153],{},"完全在用户态执行","，不涉及模式切换，所以只需要 ",[18,1156,1157],{},"几个纳秒"," 到几十个纳秒——比上下文切换快 1~2 个数量级。",[14,1160,1161,1162,1165,1166,1169],{},"这个比使用lock和上锁的用户态自旋的方式来说快了好几倍。顺便提一嘴，自旋指的是单核CPU上持有锁的线程被抢占，",[18,1163,1164],{},"自旋的线程永远等不到锁，只会浪费整个时间片","。或者是多核 CPU 上，自旋的线程可以在",[18,1167,1168],{},"另一个核心","上运行，等待持有锁的核心释放锁。",[14,1171,1172,1173,1176,1177,1180],{},"C#中有个叫做Paraller的类，",[152,1174,1175],{},"Parallel.For"," 的作用是",[18,1178,1179],{},"并行执行循环迭代","，将函数分配到多个线程上同时执行。",[145,1182,1184],{"className":546,"code":1183,"language":548,"meta":150,"style":150},"\u002F\u002F 串行版本：顺序执行，总耗时 = 20 × 100ms = 2000ms\nfor(int i = 0; i \u003C input.Length; i++)\n{\n    output[i] = HeavyJob(input[i]);  \u002F\u002F 每次等待100ms\n}\n\n\u002F\u002F 并行版本：20个任务同时执行，总耗时 ≈ 100ms（取决于CPU核心数）\nParallel.For(0, input.Length, i => output[i] = HeavyJob(input[i]));\n",[152,1185,1186,1191,1196,1200,1205,1209,1213,1218],{"__ignoreMap":150},[155,1187,1188],{"class":157,"line":158},[155,1189,1190],{},"\u002F\u002F 串行版本：顺序执行，总耗时 = 20 × 100ms = 2000ms\n",[155,1192,1193],{"class":157,"line":164},[155,1194,1195],{},"for(int i = 0; i \u003C input.Length; i++)\n",[155,1197,1198],{"class":157,"line":170},[155,1199,565],{},[155,1201,1202],{"class":157,"line":176},[155,1203,1204],{},"    output[i] = HeavyJob(input[i]);  \u002F\u002F 每次等待100ms\n",[155,1206,1207],{"class":157,"line":182},[155,1208,661],{},[155,1210,1211],{"class":157,"line":188},[155,1212,517],{"emptyLinePlaceholder":516},[155,1214,1215],{"class":157,"line":194},[155,1216,1217],{},"\u002F\u002F 并行版本：20个任务同时执行，总耗时 ≈ 100ms（取决于CPU核心数）\n",[155,1219,1220],{"class":157,"line":200},[155,1221,1222],{},"Parallel.For(0, input.Length, i => output[i] = HeavyJob(input[i]));\n",[14,1224,1225],{},"下面是Parallel.For的工作原理：",[145,1227,1229],{"className":147,"code":1228,"language":149,"meta":150,"style":150},"graph TB\n    subgraph Main [主线程]\n        A[Parallel.For 调用] --> B[任务分区器]\n    end\n    \n    subgraph Scheduler [任务调度]\n        B --> C[分区1: i=0-4]\n        B --> D[分区2: i=5-9]\n        B --> E[分区3: i=10-14]\n        B --> F[分区4: i=15-19]\n    end\n    \n    subgraph ThreadPool [线程池]\n        C --> T1[线程1 执行]\n        D --> T2[线程2 执行]\n        E --> T3[线程3 执行]\n        F --> T4[线程4 执行]\n    end\n    \n    subgraph Execution [并行执行]\n        T1 --> R1[HeavyJob 0-4\u003Cbr>各100ms]\n        T2 --> R2[HeavyJob 5-9\u003Cbr>各100ms]\n        T3 --> R3[HeavyJob 10-14\u003Cbr>各100ms]\n        T4 --> R4[HeavyJob 15-19\u003Cbr>各100ms]\n    end\n    \n    R1 --> G[合并结果到 output]\n    R2 --> G\n    R3 --> G\n    R4 --> G\n",[152,1230,1231,1235,1240,1245,1249,1253,1258,1263,1268,1273,1278,1282,1286,1291,1296,1301,1306,1311,1315,1319,1324,1329,1334,1339,1344,1348,1352,1357,1362,1367],{"__ignoreMap":150},[155,1232,1233],{"class":157,"line":158},[155,1234,304],{},[155,1236,1237],{"class":157,"line":164},[155,1238,1239],{},"    subgraph Main [主线程]\n",[155,1241,1242],{"class":157,"line":170},[155,1243,1244],{},"        A[Parallel.For 调用] --> B[任务分区器]\n",[155,1246,1247],{"class":157,"line":176},[155,1248,334],{},[155,1250,1251],{"class":157,"line":182},[155,1252,339],{},[155,1254,1255],{"class":157,"line":188},[155,1256,1257],{},"    subgraph Scheduler [任务调度]\n",[155,1259,1260],{"class":157,"line":194},[155,1261,1262],{},"        B --> C[分区1: i=0-4]\n",[155,1264,1265],{"class":157,"line":200},[155,1266,1267],{},"        B --> D[分区2: i=5-9]\n",[155,1269,1270],{"class":157,"line":206},[155,1271,1272],{},"        B --> E[分区3: i=10-14]\n",[155,1274,1275],{"class":157,"line":347},[155,1276,1277],{},"        B --> F[分区4: i=15-19]\n",[155,1279,1280],{"class":157,"line":353},[155,1281,334],{},[155,1283,1284],{"class":157,"line":358},[155,1285,339],{},[155,1287,1288],{"class":157,"line":363},[155,1289,1290],{},"    subgraph ThreadPool [线程池]\n",[155,1292,1293],{"class":157,"line":369},[155,1294,1295],{},"        C --> T1[线程1 执行]\n",[155,1297,1298],{"class":157,"line":375},[155,1299,1300],{},"        D --> T2[线程2 执行]\n",[155,1302,1303],{"class":157,"line":381},[155,1304,1305],{},"        E --> T3[线程3 执行]\n",[155,1307,1308],{"class":157,"line":387},[155,1309,1310],{},"        F --> T4[线程4 执行]\n",[155,1312,1313],{"class":157,"line":393},[155,1314,334],{},[155,1316,1317],{"class":157,"line":399},[155,1318,339],{},[155,1320,1321],{"class":157,"line":405},[155,1322,1323],{},"    subgraph Execution [并行执行]\n",[155,1325,1326],{"class":157,"line":411},[155,1327,1328],{},"        T1 --> R1[HeavyJob 0-4\u003Cbr>各100ms]\n",[155,1330,1331],{"class":157,"line":416},[155,1332,1333],{},"        T2 --> R2[HeavyJob 5-9\u003Cbr>各100ms]\n",[155,1335,1336],{"class":157,"line":421},[155,1337,1338],{},"        T3 --> R3[HeavyJob 10-14\u003Cbr>各100ms]\n",[155,1340,1341],{"class":157,"line":426},[155,1342,1343],{},"        T4 --> R4[HeavyJob 15-19\u003Cbr>各100ms]\n",[155,1345,1346],{"class":157,"line":432},[155,1347,334],{},[155,1349,1350],{"class":157,"line":438},[155,1351,339],{},[155,1353,1354],{"class":157,"line":444},[155,1355,1356],{},"    R1 --> G[合并结果到 output]\n",[155,1358,1359],{"class":157,"line":450},[155,1360,1361],{},"    R2 --> G\n",[155,1363,1364],{"class":157,"line":455},[155,1365,1366],{},"    R3 --> G\n",[155,1368,1369],{"class":157,"line":461},[155,1370,1371],{},"    R4 --> G\n",[14,1373,1374],{},"\u002F\u002F Parallel.For 使用 .NET 线程池，不会每次都创建新线程\n\u002F\u002F 线程池会根据 CPU 核心数和任务负载动态调整线程数",[14,1376,1377,1378,1381,1382,1385,1386,1381,1388,1390],{},"另一种方式是使用PLINQ，PLINQ 是 LINQ 的并行版本，通过 ",[152,1379,1380],{},".AsParallel()"," 将查询转换为",[18,1383,1384],{},"并行执行","，自动将数据分区到多个线程上处理，通过 ",[152,1387,1380],{},[18,1389,1384],{},"，自动将数据分区到多个线程上处理。",[145,1392,1394],{"className":546,"code":1393,"language":548,"meta":150,"style":150},"var input = Enumerable.Range(1, 20).ToArray();\nvar sw = Stopwatch.StartNew();\n\n\u002F\u002F PLINQ 版本\nvar output = input\n    .AsParallel()                    \u002F\u002F 启用并行\n    .Select(i => HeavyJob(i))        \u002F\u002F 并行执行 HeavyJob\n    .ToArray();                      \u002F\u002F 收集结果\n\nConsole.WriteLine(sw.ElapsedMilliseconds);\nPrintArray(output);\n\nint HeavyJob(int input)\n{\n    Thread.Sleep(100);\n    return input * input;\n}\n",[152,1395,1396,1401,1406,1410,1415,1420,1425,1430,1435,1439,1444,1449,1453,1458,1462,1467,1472],{"__ignoreMap":150},[155,1397,1398],{"class":157,"line":158},[155,1399,1400],{},"var input = Enumerable.Range(1, 20).ToArray();\n",[155,1402,1403],{"class":157,"line":164},[155,1404,1405],{},"var sw = Stopwatch.StartNew();\n",[155,1407,1408],{"class":157,"line":170},[155,1409,517],{"emptyLinePlaceholder":516},[155,1411,1412],{"class":157,"line":176},[155,1413,1414],{},"\u002F\u002F PLINQ 版本\n",[155,1416,1417],{"class":157,"line":182},[155,1418,1419],{},"var output = input\n",[155,1421,1422],{"class":157,"line":188},[155,1423,1424],{},"    .AsParallel()                    \u002F\u002F 启用并行\n",[155,1426,1427],{"class":157,"line":194},[155,1428,1429],{},"    .Select(i => HeavyJob(i))        \u002F\u002F 并行执行 HeavyJob\n",[155,1431,1432],{"class":157,"line":200},[155,1433,1434],{},"    .ToArray();                      \u002F\u002F 收集结果\n",[155,1436,1437],{"class":157,"line":206},[155,1438,517],{"emptyLinePlaceholder":516},[155,1440,1441],{"class":157,"line":347},[155,1442,1443],{},"Console.WriteLine(sw.ElapsedMilliseconds);\n",[155,1445,1446],{"class":157,"line":353},[155,1447,1448],{},"PrintArray(output);\n",[155,1450,1451],{"class":157,"line":358},[155,1452,517],{"emptyLinePlaceholder":516},[155,1454,1455],{"class":157,"line":363},[155,1456,1457],{},"int HeavyJob(int input)\n",[155,1459,1460],{"class":157,"line":369},[155,1461,565],{},[155,1463,1464],{"class":157,"line":375},[155,1465,1466],{},"    Thread.Sleep(100);\n",[155,1468,1469],{"class":157,"line":381},[155,1470,1471],{},"    return input * input;\n",[155,1473,1474],{"class":157,"line":387},[155,1475,661],{},[289,1477,1478],{"id":1478},"线程启动",[14,1480,1481],{},"当我们使用Thread的时候，需要使用Thread.Start方法来启动线程。",[145,1483,1485],{"className":546,"code":1484,"language":548,"meta":150,"style":150},"using System;\nusing System.Threading;\npublic class C{\n    public void M(){\n        var th1 = new Thread(ThreadMethod1);\n        var th2 = new Thread(ThreadMethod2);\n        th1.Start();\n        th2.Start();\n            \n    }\n    void ThreadMethod1(){}\n    void ThreadMethod2(object? obj){}\n}\n",[152,1486,1487,1492,1497,1502,1507,1512,1517,1522,1527,1532,1536,1541,1546],{"__ignoreMap":150},[155,1488,1489],{"class":157,"line":158},[155,1490,1491],{},"using System;\n",[155,1493,1494],{"class":157,"line":164},[155,1495,1496],{},"using System.Threading;\n",[155,1498,1499],{"class":157,"line":170},[155,1500,1501],{},"public class C{\n",[155,1503,1504],{"class":157,"line":176},[155,1505,1506],{},"    public void M(){\n",[155,1508,1509],{"class":157,"line":182},[155,1510,1511],{},"        var th1 = new Thread(ThreadMethod1);\n",[155,1513,1514],{"class":157,"line":188},[155,1515,1516],{},"        var th2 = new Thread(ThreadMethod2);\n",[155,1518,1519],{"class":157,"line":194},[155,1520,1521],{},"        th1.Start();\n",[155,1523,1524],{"class":157,"line":200},[155,1525,1526],{},"        th2.Start();\n",[155,1528,1529],{"class":157,"line":206},[155,1530,1531],{},"            \n",[155,1533,1534],{"class":157,"line":347},[155,1535,656],{},[155,1537,1538],{"class":157,"line":353},[155,1539,1540],{},"    void ThreadMethod1(){}\n",[155,1542,1543],{"class":157,"line":358},[155,1544,1545],{},"    void ThreadMethod2(object? obj){}\n",[155,1547,1548],{"class":157,"line":363},[155,1549,661],{},[14,1551,1552],{},"这段代码会被编译器表示为IL代码：",[145,1554,1556],{"className":546,"code":1555,"language":548,"meta":150,"style":150},"using System.Diagnostics;\nusing System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Security;\nusing System.Security.Permissions;\nusing System.Threading;\n\n[assembly: CompilationRelaxations(8)]\n[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]\n[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue | DebuggableAttribute.DebuggingModes.DisableOptimizations)]\n[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]\n[assembly: AssemblyVersion(\"0.0.0.0\")]\n[module: UnverifiableCode]\n[module: RefSafetyRules(11)]\n\npublic class C\n{\n    public void M()\n    {\n        Thread thread = new Thread(new ThreadStart(ThreadMethod1));\n        Thread thread2 = new Thread(new ParameterizedThreadStart(ThreadMethod2));\n        thread.Start();\n        thread2.Start();\n    }\n\n    private void ThreadMethod1()\n    {\n    }\n\n    [NullableContext(2)]\n    private void ThreadMethod2(object obj)\n    {\n    }\n}\n\n",[152,1557,1558,1562,1567,1572,1577,1582,1586,1590,1595,1600,1605,1610,1615,1620,1625,1629,1634,1638,1643,1647,1652,1657,1662,1667,1671,1675,1680,1684,1688,1692,1697,1702,1706,1710],{"__ignoreMap":150},[155,1559,1560],{"class":157,"line":158},[155,1561,752],{},[155,1563,1564],{"class":157,"line":164},[155,1565,1566],{},"using System.Reflection;\n",[155,1568,1569],{"class":157,"line":170},[155,1570,1571],{},"using System.Runtime.CompilerServices;\n",[155,1573,1574],{"class":157,"line":176},[155,1575,1576],{},"using System.Security;\n",[155,1578,1579],{"class":157,"line":182},[155,1580,1581],{},"using System.Security.Permissions;\n",[155,1583,1584],{"class":157,"line":188},[155,1585,1496],{},[155,1587,1588],{"class":157,"line":194},[155,1589,517],{"emptyLinePlaceholder":516},[155,1591,1592],{"class":157,"line":200},[155,1593,1594],{},"[assembly: CompilationRelaxations(8)]\n",[155,1596,1597],{"class":157,"line":206},[155,1598,1599],{},"[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]\n",[155,1601,1602],{"class":157,"line":347},[155,1603,1604],{},"[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue | DebuggableAttribute.DebuggingModes.DisableOptimizations)]\n",[155,1606,1607],{"class":157,"line":353},[155,1608,1609],{},"[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]\n",[155,1611,1612],{"class":157,"line":358},[155,1613,1614],{},"[assembly: AssemblyVersion(\"0.0.0.0\")]\n",[155,1616,1617],{"class":157,"line":363},[155,1618,1619],{},"[module: UnverifiableCode]\n",[155,1621,1622],{"class":157,"line":369},[155,1623,1624],{},"[module: RefSafetyRules(11)]\n",[155,1626,1627],{"class":157,"line":375},[155,1628,517],{"emptyLinePlaceholder":516},[155,1630,1631],{"class":157,"line":381},[155,1632,1633],{},"public class C\n",[155,1635,1636],{"class":157,"line":387},[155,1637,565],{},[155,1639,1640],{"class":157,"line":393},[155,1641,1642],{},"    public void M()\n",[155,1644,1645],{"class":157,"line":399},[155,1646,607],{},[155,1648,1649],{"class":157,"line":405},[155,1650,1651],{},"        Thread thread = new Thread(new ThreadStart(ThreadMethod1));\n",[155,1653,1654],{"class":157,"line":411},[155,1655,1656],{},"        Thread thread2 = new Thread(new ParameterizedThreadStart(ThreadMethod2));\n",[155,1658,1659],{"class":157,"line":416},[155,1660,1661],{},"        thread.Start();\n",[155,1663,1664],{"class":157,"line":421},[155,1665,1666],{},"        thread2.Start();\n",[155,1668,1669],{"class":157,"line":426},[155,1670,656],{},[155,1672,1673],{"class":157,"line":432},[155,1674,517],{"emptyLinePlaceholder":516},[155,1676,1677],{"class":157,"line":438},[155,1678,1679],{},"    private void ThreadMethod1()\n",[155,1681,1682],{"class":157,"line":444},[155,1683,607],{},[155,1685,1686],{"class":157,"line":450},[155,1687,656],{},[155,1689,1690],{"class":157,"line":455},[155,1691,517],{"emptyLinePlaceholder":516},[155,1693,1694],{"class":157,"line":461},[155,1695,1696],{},"    [NullableContext(2)]\n",[155,1698,1699],{"class":157,"line":467},[155,1700,1701],{},"    private void ThreadMethod2(object obj)\n",[155,1703,1704],{"class":157,"line":473},[155,1705,607],{},[155,1707,1708],{"class":157,"line":479},[155,1709,656],{},[155,1711,1712],{"class":157,"line":484},[155,1713,661],{},[14,1715,1716],{},"其中，我们的thread 内部使用了ThreadStart(ThreadMethod1)，这是一个无参委托。而我们的thread2使用了ParameterizedThreadStart，这就是ThreadStart的含参形式，可以传递参数。",[145,1718,1720],{"className":546,"code":1719,"language":548,"meta":150,"style":150},"\u002F\u002F 定义\npublic delegate void ThreadStart();\n\n\u002F\u002F 使用\nThread thread = new Thread(new ThreadStart(ThreadMethod1));\nthread.Start();\n\n\u002F\u002F 或简写\nThread thread = new Thread(ThreadMethod1);  \u002F\u002F 编译器自动转换\nthread.Start();\n\n\u002F\u002F 定义\npublic delegate void ParameterizedThreadStart(object obj);\n\u002F\u002F 使用\nThread thread = new Thread(new ParameterizedThreadStart(ThreadMethod2));\nthread.Start(\"hello\");  \u002F\u002F 传递参数\n\u002F\u002F 或简写\nThread thread = new Thread(ThreadMethod2);\nthread.Start(42);\n\u002F\u002F 方法内部需要转换类型\nprivate void ThreadMethod2(object obj)\n{\n    int value = (int)obj;  \u002F\u002F 需要强制转换\n    Console.WriteLine(value);\n}\n",[152,1721,1722,1727,1732,1736,1741,1746,1751,1755,1760,1765,1769,1773,1777,1782,1786,1791,1796,1800,1805,1810,1815,1820,1824,1829,1834],{"__ignoreMap":150},[155,1723,1724],{"class":157,"line":158},[155,1725,1726],{},"\u002F\u002F 定义\n",[155,1728,1729],{"class":157,"line":164},[155,1730,1731],{},"public delegate void ThreadStart();\n",[155,1733,1734],{"class":157,"line":170},[155,1735,517],{"emptyLinePlaceholder":516},[155,1737,1738],{"class":157,"line":176},[155,1739,1740],{},"\u002F\u002F 使用\n",[155,1742,1743],{"class":157,"line":182},[155,1744,1745],{},"Thread thread = new Thread(new ThreadStart(ThreadMethod1));\n",[155,1747,1748],{"class":157,"line":188},[155,1749,1750],{},"thread.Start();\n",[155,1752,1753],{"class":157,"line":194},[155,1754,517],{"emptyLinePlaceholder":516},[155,1756,1757],{"class":157,"line":200},[155,1758,1759],{},"\u002F\u002F 或简写\n",[155,1761,1762],{"class":157,"line":206},[155,1763,1764],{},"Thread thread = new Thread(ThreadMethod1);  \u002F\u002F 编译器自动转换\n",[155,1766,1767],{"class":157,"line":347},[155,1768,1750],{},[155,1770,1771],{"class":157,"line":353},[155,1772,517],{"emptyLinePlaceholder":516},[155,1774,1775],{"class":157,"line":358},[155,1776,1726],{},[155,1778,1779],{"class":157,"line":363},[155,1780,1781],{},"public delegate void ParameterizedThreadStart(object obj);\n",[155,1783,1784],{"class":157,"line":369},[155,1785,1740],{},[155,1787,1788],{"class":157,"line":375},[155,1789,1790],{},"Thread thread = new Thread(new ParameterizedThreadStart(ThreadMethod2));\n",[155,1792,1793],{"class":157,"line":381},[155,1794,1795],{},"thread.Start(\"hello\");  \u002F\u002F 传递参数\n",[155,1797,1798],{"class":157,"line":387},[155,1799,1759],{},[155,1801,1802],{"class":157,"line":393},[155,1803,1804],{},"Thread thread = new Thread(ThreadMethod2);\n",[155,1806,1807],{"class":157,"line":399},[155,1808,1809],{},"thread.Start(42);\n",[155,1811,1812],{"class":157,"line":405},[155,1813,1814],{},"\u002F\u002F 方法内部需要转换类型\n",[155,1816,1817],{"class":157,"line":411},[155,1818,1819],{},"private void ThreadMethod2(object obj)\n",[155,1821,1822],{"class":157,"line":416},[155,1823,565],{},[155,1825,1826],{"class":157,"line":421},[155,1827,1828],{},"    int value = (int)obj;  \u002F\u002F 需要强制转换\n",[155,1830,1831],{"class":157,"line":426},[155,1832,1833],{},"    Console.WriteLine(value);\n",[155,1835,1836],{"class":157,"line":432},[155,1837,661],{},[14,1839,1840,1841,29],{},"Thread对象有一个叫做IsBackground的属性，我们可以用于设置线程是否为",[18,1842,1843],{},"后台线程",[145,1845,1847],{"className":546,"code":1846,"language":548,"meta":150,"style":150},"Thread thread = new Thread(ThreadMethod);\nthread.IsBackground = true;  \u002F\u002F 设为后台线程\nthread.Start();\n或者是 new Thread(){IsBackground = true, Priority = ThreadPriority.Highest} \n\u002F\u002F 设置优先级\n",[152,1848,1849,1854,1859,1863,1868],{"__ignoreMap":150},[155,1850,1851],{"class":157,"line":158},[155,1852,1853],{},"Thread thread = new Thread(ThreadMethod);\n",[155,1855,1856],{"class":157,"line":164},[155,1857,1858],{},"thread.IsBackground = true;  \u002F\u002F 设为后台线程\n",[155,1860,1861],{"class":157,"line":170},[155,1862,1750],{},[155,1864,1865],{"class":157,"line":176},[155,1866,1867],{},"或者是 new Thread(){IsBackground = true, Priority = ThreadPriority.Highest} \n",[155,1869,1870],{"class":157,"line":182},[155,1871,1872],{},"\u002F\u002F 设置优先级\n",[289,1874,1876],{"id":1875},"waithandle","WaitHandle",[14,1878,1879,1881,1882,1885,1886,1889,1890,29],{},[152,1880,1876],{}," 是 .NET 中",[18,1883,1884],{},"所有等待机制的抽象基类","，封装了操作系统级别的",[18,1887,1888],{},"同步对象","，用于",[18,1891,1892],{},"线程间的信号通知和等待",[14,1894,1895],{},"包含了ManualResetEvent和AutoResetEvent，与信号量Semaphore相关。前者表示手动重置事件，后者表示自动重置事件。 Semaphore是用来控制同时访问资源的线程数量的，可以跨进程使用。",[145,1897,1899],{"className":546,"code":1898,"language":548,"meta":150,"style":150},"public class ManualResetEventExample\n{\n    private static ManualResetEvent _event = new ManualResetEvent(false);  \u002F\u002F 初始非信号状态\n    \n    public static void Main()\n    {\n        \u002F\u002F 启动多个工作线程\n        for (int i = 1; i \u003C= 5; i++)\n        {\n            int id = i;\n            Thread worker = new Thread(() => Worker(id));\n            worker.Start();\n        }\n        \n        Thread.Sleep(2000);\n        Console.WriteLine(\"按下任意键开始所有工作线程...\");\n        Console.ReadKey();\n        \n        \u002F\u002F 一次性唤醒所有等待的线程\n        _event.Set();  \u002F\u002F 设置为 signaled，唤醒所有等待线程\n        \n        Thread.Sleep(2000);\n        Console.WriteLine(\"重置事件，线程将再次等待\");\n        _event.Reset();  \u002F\u002F 重置为非信号状态\n    }\n    \n    static void Worker(int id)\n    {\n        Console.WriteLine($\"线程 {id} 等待信号...\");\n        _event.WaitOne();  \u002F\u002F 阻塞直到收到信号\n        Console.WriteLine($\"线程 {id} 开始工作\");\n    }\n}\n\n\u002F\u002F 输出：\n\u002F\u002F 线程 1 等待信号...\n\u002F\u002F 线程 2 等待信号...\n\u002F\u002F ...\n\u002F\u002F 按下任意键开始所有工作线程...\n\u002F\u002F 线程 1 开始工作\n\u002F\u002F 线程 2 开始工作\n\u002F\u002F ... (所有线程同时被唤醒)\n\n\n\npublic class AutoResetEventExample\n{\n    private static AutoResetEvent _event = new AutoResetEvent(false);  \u002F\u002F 初始非信号状态\n    private static int _data = 0;\n    \n    public static void Main()\n    {\n        Thread producer = new Thread(Producer);\n        Thread consumer = new Thread(Consumer);\n        \n        producer.Start();\n        consumer.Start();\n        \n        producer.Join();\n        consumer.Join();\n    }\n    \n    static void Producer()\n    {\n        for (int i = 1; i \u003C= 5; i++)\n        {\n            _data = i;\n            Console.WriteLine($\"生产数据: {i}\");\n            \n            \u002F\u002F 通知消费者数据已就绪\n            _event.Set();  \u002F\u002F 设置为 signaled，唤醒一个等待线程\n            \n            Thread.Sleep(1000);\n        }\n    }\n    \n    static void Consumer()\n    {\n        for (int i = 1; i \u003C= 5; i++)\n        {\n            \u002F\u002F 等待数据\n            _event.WaitOne();  \u002F\u002F 阻塞直到收到信号\n            Console.WriteLine($\"消费数据: {_data}\");\n        }\n    }\n}\n\n\u002F\u002F 输出：\n\u002F\u002F 生产数据: 1\n\u002F\u002F 消费数据: 1\n\u002F\u002F 生产数据: 2\n\u002F\u002F 消费数据: 2\n\u002F\u002F ...\n\n\npublic class SemaphoreExample\n{\n    \u002F\u002F 最多允许 3 个线程同时访问\n    private static Semaphore _semaphore = new Semaphore(3, 3);\n    private static int _resourceCount = 0;\n    \n    public static void Main()\n    {\n        \u002F\u002F 启动 10 个线程\n        for (int i = 1; i \u003C= 10; i++)\n        {\n            int id = i;\n            Thread t = new Thread(() => Worker(id));\n            t.Start();\n        }\n        \n        Thread.Sleep(15000);\n    }\n    \n    static void Worker(int id)\n    {\n        Console.WriteLine($\"线程 {id} 等待进入...\");\n        _semaphore.WaitOne();  \u002F\u002F 请求资源\n        \n        \u002F\u002F 进入临界区\n        Interlocked.Increment(ref _resourceCount);\n        Console.WriteLine($\"线程 {id} 进入，当前活动线程: {_resourceCount}\");\n        \n        Thread.Sleep(2000);  \u002F\u002F 模拟工作\n        \n        \u002F\u002F 离开临界区\n        Interlocked.Decrement(ref _resourceCount);\n        Console.WriteLine($\"线程 {id} 离开，当前活动线程: {_resourceCount}\");\n        \n        _semaphore.Release();  \u002F\u002F 释放资源\n    }\n}\n\n\u002F\u002F 输出：\n\u002F\u002F 线程 1 等待进入...\n\u002F\u002F 线程 1 进入，当前活动线程: 1\n\u002F\u002F 线程 2 等待进入...\n\u002F\u002F 线程 2 进入，当前活动线程: 2\n\u002F\u002F 线程 3 等待进入...\n\u002F\u002F 线程 3 进入，当前活动线程: 3\n\u002F\u002F 线程 4 等待进入...  (被阻塞，因为已有 3 个线程)\n\u002F\u002F 线程 5 等待进入...  (被阻塞)\n\u002F\u002F ...\n\u002F\u002F (2 秒后，线程 1 离开，线程 4 进入)\n",[152,1900,1901,1906,1910,1915,1919,1924,1928,1933,1938,1942,1947,1952,1957,1961,1965,1970,1975,1980,1984,1989,1994,1998,2002,2007,2012,2016,2020,2025,2029,2034,2039,2044,2048,2052,2056,2062,2068,2074,2080,2086,2092,2098,2104,2109,2114,2119,2125,2130,2136,2142,2147,2152,2157,2163,2169,2174,2180,2186,2191,2197,2203,2208,2213,2219,2224,2229,2234,2240,2246,2251,2257,2263,2268,2274,2279,2284,2289,2295,2300,2305,2310,2316,2322,2328,2333,2338,2343,2348,2353,2359,2365,2371,2377,2382,2387,2392,2398,2403,2409,2415,2421,2426,2431,2436,2442,2448,2453,2458,2464,2470,2475,2480,2486,2491,2496,2501,2506,2512,2518,2523,2529,2535,2541,2546,2552,2557,2563,2569,2575,2580,2586,2591,2596,2601,2606,2612,2618,2624,2630,2636,2642,2648,2654,2659],{"__ignoreMap":150},[155,1902,1903],{"class":157,"line":158},[155,1904,1905],{},"public class ManualResetEventExample\n",[155,1907,1908],{"class":157,"line":164},[155,1909,565],{},[155,1911,1912],{"class":157,"line":170},[155,1913,1914],{},"    private static ManualResetEvent _event = new ManualResetEvent(false);  \u002F\u002F 初始非信号状态\n",[155,1916,1917],{"class":157,"line":176},[155,1918,339],{},[155,1920,1921],{"class":157,"line":182},[155,1922,1923],{},"    public static void Main()\n",[155,1925,1926],{"class":157,"line":188},[155,1927,607],{},[155,1929,1930],{"class":157,"line":194},[155,1931,1932],{},"        \u002F\u002F 启动多个工作线程\n",[155,1934,1935],{"class":157,"line":200},[155,1936,1937],{},"        for (int i = 1; i \u003C= 5; i++)\n",[155,1939,1940],{"class":157,"line":206},[155,1941,1011],{},[155,1943,1944],{"class":157,"line":347},[155,1945,1946],{},"            int id = i;\n",[155,1948,1949],{"class":157,"line":353},[155,1950,1951],{},"            Thread worker = new Thread(() => Worker(id));\n",[155,1953,1954],{"class":157,"line":358},[155,1955,1956],{},"            worker.Start();\n",[155,1958,1959],{"class":157,"line":363},[155,1960,1031],{},[155,1962,1963],{"class":157,"line":369},[155,1964,627],{},[155,1966,1967],{"class":157,"line":375},[155,1968,1969],{},"        Thread.Sleep(2000);\n",[155,1971,1972],{"class":157,"line":381},[155,1973,1974],{},"        Console.WriteLine(\"按下任意键开始所有工作线程...\");\n",[155,1976,1977],{"class":157,"line":387},[155,1978,1979],{},"        Console.ReadKey();\n",[155,1981,1982],{"class":157,"line":393},[155,1983,627],{},[155,1985,1986],{"class":157,"line":399},[155,1987,1988],{},"        \u002F\u002F 一次性唤醒所有等待的线程\n",[155,1990,1991],{"class":157,"line":405},[155,1992,1993],{},"        _event.Set();  \u002F\u002F 设置为 signaled，唤醒所有等待线程\n",[155,1995,1996],{"class":157,"line":411},[155,1997,627],{},[155,1999,2000],{"class":157,"line":416},[155,2001,1969],{},[155,2003,2004],{"class":157,"line":421},[155,2005,2006],{},"        Console.WriteLine(\"重置事件，线程将再次等待\");\n",[155,2008,2009],{"class":157,"line":426},[155,2010,2011],{},"        _event.Reset();  \u002F\u002F 重置为非信号状态\n",[155,2013,2014],{"class":157,"line":432},[155,2015,656],{},[155,2017,2018],{"class":157,"line":438},[155,2019,339],{},[155,2021,2022],{"class":157,"line":444},[155,2023,2024],{},"    static void Worker(int id)\n",[155,2026,2027],{"class":157,"line":450},[155,2028,607],{},[155,2030,2031],{"class":157,"line":455},[155,2032,2033],{},"        Console.WriteLine($\"线程 {id} 等待信号...\");\n",[155,2035,2036],{"class":157,"line":461},[155,2037,2038],{},"        _event.WaitOne();  \u002F\u002F 阻塞直到收到信号\n",[155,2040,2041],{"class":157,"line":467},[155,2042,2043],{},"        Console.WriteLine($\"线程 {id} 开始工作\");\n",[155,2045,2046],{"class":157,"line":473},[155,2047,656],{},[155,2049,2050],{"class":157,"line":479},[155,2051,661],{},[155,2053,2054],{"class":157,"line":484},[155,2055,517],{"emptyLinePlaceholder":516},[155,2057,2059],{"class":157,"line":2058},35,[155,2060,2061],{},"\u002F\u002F 输出：\n",[155,2063,2065],{"class":157,"line":2064},36,[155,2066,2067],{},"\u002F\u002F 线程 1 等待信号...\n",[155,2069,2071],{"class":157,"line":2070},37,[155,2072,2073],{},"\u002F\u002F 线程 2 等待信号...\n",[155,2075,2077],{"class":157,"line":2076},38,[155,2078,2079],{},"\u002F\u002F ...\n",[155,2081,2083],{"class":157,"line":2082},39,[155,2084,2085],{},"\u002F\u002F 按下任意键开始所有工作线程...\n",[155,2087,2089],{"class":157,"line":2088},40,[155,2090,2091],{},"\u002F\u002F 线程 1 开始工作\n",[155,2093,2095],{"class":157,"line":2094},41,[155,2096,2097],{},"\u002F\u002F 线程 2 开始工作\n",[155,2099,2101],{"class":157,"line":2100},42,[155,2102,2103],{},"\u002F\u002F ... (所有线程同时被唤醒)\n",[155,2105,2107],{"class":157,"line":2106},43,[155,2108,517],{"emptyLinePlaceholder":516},[155,2110,2112],{"class":157,"line":2111},44,[155,2113,517],{"emptyLinePlaceholder":516},[155,2115,2117],{"class":157,"line":2116},45,[155,2118,517],{"emptyLinePlaceholder":516},[155,2120,2122],{"class":157,"line":2121},46,[155,2123,2124],{},"public class AutoResetEventExample\n",[155,2126,2128],{"class":157,"line":2127},47,[155,2129,565],{},[155,2131,2133],{"class":157,"line":2132},48,[155,2134,2135],{},"    private static AutoResetEvent _event = new AutoResetEvent(false);  \u002F\u002F 初始非信号状态\n",[155,2137,2139],{"class":157,"line":2138},49,[155,2140,2141],{},"    private static int _data = 0;\n",[155,2143,2145],{"class":157,"line":2144},50,[155,2146,339],{},[155,2148,2150],{"class":157,"line":2149},51,[155,2151,1923],{},[155,2153,2155],{"class":157,"line":2154},52,[155,2156,607],{},[155,2158,2160],{"class":157,"line":2159},53,[155,2161,2162],{},"        Thread producer = new Thread(Producer);\n",[155,2164,2166],{"class":157,"line":2165},54,[155,2167,2168],{},"        Thread consumer = new Thread(Consumer);\n",[155,2170,2172],{"class":157,"line":2171},55,[155,2173,627],{},[155,2175,2177],{"class":157,"line":2176},56,[155,2178,2179],{},"        producer.Start();\n",[155,2181,2183],{"class":157,"line":2182},57,[155,2184,2185],{},"        consumer.Start();\n",[155,2187,2189],{"class":157,"line":2188},58,[155,2190,627],{},[155,2192,2194],{"class":157,"line":2193},59,[155,2195,2196],{},"        producer.Join();\n",[155,2198,2200],{"class":157,"line":2199},60,[155,2201,2202],{},"        consumer.Join();\n",[155,2204,2206],{"class":157,"line":2205},61,[155,2207,656],{},[155,2209,2211],{"class":157,"line":2210},62,[155,2212,339],{},[155,2214,2216],{"class":157,"line":2215},63,[155,2217,2218],{},"    static void Producer()\n",[155,2220,2222],{"class":157,"line":2221},64,[155,2223,607],{},[155,2225,2227],{"class":157,"line":2226},65,[155,2228,1937],{},[155,2230,2232],{"class":157,"line":2231},66,[155,2233,1011],{},[155,2235,2237],{"class":157,"line":2236},67,[155,2238,2239],{},"            _data = i;\n",[155,2241,2243],{"class":157,"line":2242},68,[155,2244,2245],{},"            Console.WriteLine($\"生产数据: {i}\");\n",[155,2247,2249],{"class":157,"line":2248},69,[155,2250,1531],{},[155,2252,2254],{"class":157,"line":2253},70,[155,2255,2256],{},"            \u002F\u002F 通知消费者数据已就绪\n",[155,2258,2260],{"class":157,"line":2259},71,[155,2261,2262],{},"            _event.Set();  \u002F\u002F 设置为 signaled，唤醒一个等待线程\n",[155,2264,2266],{"class":157,"line":2265},72,[155,2267,1531],{},[155,2269,2271],{"class":157,"line":2270},73,[155,2272,2273],{},"            Thread.Sleep(1000);\n",[155,2275,2277],{"class":157,"line":2276},74,[155,2278,1031],{},[155,2280,2282],{"class":157,"line":2281},75,[155,2283,656],{},[155,2285,2287],{"class":157,"line":2286},76,[155,2288,339],{},[155,2290,2292],{"class":157,"line":2291},77,[155,2293,2294],{},"    static void Consumer()\n",[155,2296,2298],{"class":157,"line":2297},78,[155,2299,607],{},[155,2301,2303],{"class":157,"line":2302},79,[155,2304,1937],{},[155,2306,2308],{"class":157,"line":2307},80,[155,2309,1011],{},[155,2311,2313],{"class":157,"line":2312},81,[155,2314,2315],{},"            \u002F\u002F 等待数据\n",[155,2317,2319],{"class":157,"line":2318},82,[155,2320,2321],{},"            _event.WaitOne();  \u002F\u002F 阻塞直到收到信号\n",[155,2323,2325],{"class":157,"line":2324},83,[155,2326,2327],{},"            Console.WriteLine($\"消费数据: {_data}\");\n",[155,2329,2331],{"class":157,"line":2330},84,[155,2332,1031],{},[155,2334,2336],{"class":157,"line":2335},85,[155,2337,656],{},[155,2339,2341],{"class":157,"line":2340},86,[155,2342,661],{},[155,2344,2346],{"class":157,"line":2345},87,[155,2347,517],{"emptyLinePlaceholder":516},[155,2349,2351],{"class":157,"line":2350},88,[155,2352,2061],{},[155,2354,2356],{"class":157,"line":2355},89,[155,2357,2358],{},"\u002F\u002F 生产数据: 1\n",[155,2360,2362],{"class":157,"line":2361},90,[155,2363,2364],{},"\u002F\u002F 消费数据: 1\n",[155,2366,2368],{"class":157,"line":2367},91,[155,2369,2370],{},"\u002F\u002F 生产数据: 2\n",[155,2372,2374],{"class":157,"line":2373},92,[155,2375,2376],{},"\u002F\u002F 消费数据: 2\n",[155,2378,2380],{"class":157,"line":2379},93,[155,2381,2079],{},[155,2383,2385],{"class":157,"line":2384},94,[155,2386,517],{"emptyLinePlaceholder":516},[155,2388,2390],{"class":157,"line":2389},95,[155,2391,517],{"emptyLinePlaceholder":516},[155,2393,2395],{"class":157,"line":2394},96,[155,2396,2397],{},"public class SemaphoreExample\n",[155,2399,2401],{"class":157,"line":2400},97,[155,2402,565],{},[155,2404,2406],{"class":157,"line":2405},98,[155,2407,2408],{},"    \u002F\u002F 最多允许 3 个线程同时访问\n",[155,2410,2412],{"class":157,"line":2411},99,[155,2413,2414],{},"    private static Semaphore _semaphore = new Semaphore(3, 3);\n",[155,2416,2418],{"class":157,"line":2417},100,[155,2419,2420],{},"    private static int _resourceCount = 0;\n",[155,2422,2424],{"class":157,"line":2423},101,[155,2425,339],{},[155,2427,2429],{"class":157,"line":2428},102,[155,2430,1923],{},[155,2432,2434],{"class":157,"line":2433},103,[155,2435,607],{},[155,2437,2439],{"class":157,"line":2438},104,[155,2440,2441],{},"        \u002F\u002F 启动 10 个线程\n",[155,2443,2445],{"class":157,"line":2444},105,[155,2446,2447],{},"        for (int i = 1; i \u003C= 10; i++)\n",[155,2449,2451],{"class":157,"line":2450},106,[155,2452,1011],{},[155,2454,2456],{"class":157,"line":2455},107,[155,2457,1946],{},[155,2459,2461],{"class":157,"line":2460},108,[155,2462,2463],{},"            Thread t = new Thread(() => Worker(id));\n",[155,2465,2467],{"class":157,"line":2466},109,[155,2468,2469],{},"            t.Start();\n",[155,2471,2473],{"class":157,"line":2472},110,[155,2474,1031],{},[155,2476,2478],{"class":157,"line":2477},111,[155,2479,627],{},[155,2481,2483],{"class":157,"line":2482},112,[155,2484,2485],{},"        Thread.Sleep(15000);\n",[155,2487,2489],{"class":157,"line":2488},113,[155,2490,656],{},[155,2492,2494],{"class":157,"line":2493},114,[155,2495,339],{},[155,2497,2499],{"class":157,"line":2498},115,[155,2500,2024],{},[155,2502,2504],{"class":157,"line":2503},116,[155,2505,607],{},[155,2507,2509],{"class":157,"line":2508},117,[155,2510,2511],{},"        Console.WriteLine($\"线程 {id} 等待进入...\");\n",[155,2513,2515],{"class":157,"line":2514},118,[155,2516,2517],{},"        _semaphore.WaitOne();  \u002F\u002F 请求资源\n",[155,2519,2521],{"class":157,"line":2520},119,[155,2522,627],{},[155,2524,2526],{"class":157,"line":2525},120,[155,2527,2528],{},"        \u002F\u002F 进入临界区\n",[155,2530,2532],{"class":157,"line":2531},121,[155,2533,2534],{},"        Interlocked.Increment(ref _resourceCount);\n",[155,2536,2538],{"class":157,"line":2537},122,[155,2539,2540],{},"        Console.WriteLine($\"线程 {id} 进入，当前活动线程: {_resourceCount}\");\n",[155,2542,2544],{"class":157,"line":2543},123,[155,2545,627],{},[155,2547,2549],{"class":157,"line":2548},124,[155,2550,2551],{},"        Thread.Sleep(2000);  \u002F\u002F 模拟工作\n",[155,2553,2555],{"class":157,"line":2554},125,[155,2556,627],{},[155,2558,2560],{"class":157,"line":2559},126,[155,2561,2562],{},"        \u002F\u002F 离开临界区\n",[155,2564,2566],{"class":157,"line":2565},127,[155,2567,2568],{},"        Interlocked.Decrement(ref _resourceCount);\n",[155,2570,2572],{"class":157,"line":2571},128,[155,2573,2574],{},"        Console.WriteLine($\"线程 {id} 离开，当前活动线程: {_resourceCount}\");\n",[155,2576,2578],{"class":157,"line":2577},129,[155,2579,627],{},[155,2581,2583],{"class":157,"line":2582},130,[155,2584,2585],{},"        _semaphore.Release();  \u002F\u002F 释放资源\n",[155,2587,2589],{"class":157,"line":2588},131,[155,2590,656],{},[155,2592,2594],{"class":157,"line":2593},132,[155,2595,661],{},[155,2597,2599],{"class":157,"line":2598},133,[155,2600,517],{"emptyLinePlaceholder":516},[155,2602,2604],{"class":157,"line":2603},134,[155,2605,2061],{},[155,2607,2609],{"class":157,"line":2608},135,[155,2610,2611],{},"\u002F\u002F 线程 1 等待进入...\n",[155,2613,2615],{"class":157,"line":2614},136,[155,2616,2617],{},"\u002F\u002F 线程 1 进入，当前活动线程: 1\n",[155,2619,2621],{"class":157,"line":2620},137,[155,2622,2623],{},"\u002F\u002F 线程 2 等待进入...\n",[155,2625,2627],{"class":157,"line":2626},138,[155,2628,2629],{},"\u002F\u002F 线程 2 进入，当前活动线程: 2\n",[155,2631,2633],{"class":157,"line":2632},139,[155,2634,2635],{},"\u002F\u002F 线程 3 等待进入...\n",[155,2637,2639],{"class":157,"line":2638},140,[155,2640,2641],{},"\u002F\u002F 线程 3 进入，当前活动线程: 3\n",[155,2643,2645],{"class":157,"line":2644},141,[155,2646,2647],{},"\u002F\u002F 线程 4 等待进入...  (被阻塞，因为已有 3 个线程)\n",[155,2649,2651],{"class":157,"line":2650},142,[155,2652,2653],{},"\u002F\u002F 线程 5 等待进入...  (被阻塞)\n",[155,2655,2657],{"class":157,"line":2656},143,[155,2658,2079],{},[155,2660,2662],{"class":157,"line":2661},144,[155,2663,2664],{},"\u002F\u002F (2 秒后，线程 1 离开，线程 4 进入)\n",[289,2666,2668],{"id":2667},"task","Task",[14,2670,2671],{},"先不说Task的功能有什么，先说没有Task就会出现什么问题，首先是无法进行await，因此就无法进行等待，并且也无法进行异常聚合，虽然async 也是状态机，但是缺少记录状态的Task对象，你也无法通过.result去获取结果。",[145,2673,2675],{"className":546,"code":2674,"language":548,"meta":150,"style":150},"public async Task TaskWithException()\n{\n    await Task.Delay(100);\n    throw new InvalidOperationException(\"Task 异常\");\n}\n\npublic async Task AnotherTaskWithException()\n{\n    await Task.Delay(200);\n    throw new ArgumentException(\"另一个 Task 异常\");\n}\n\npublic async Task DemonstrateAggregation()\n{\n    var task1 = TaskWithException();\n    var task2 = AnotherTaskWithException();\n    \n    try\n    {\n        \u002F\u002F ✅ 所有异常被聚合到一个 AggregateException 中\n        await Task.WhenAll(task1, task2);\n    }\n    catch (AggregateException ex)  \u002F\u002F 注意：这里捕获的是 AggregateException\n    {\n        Console.WriteLine($\"聚合异常数量: {ex.InnerExceptions.Count}\");\n        foreach (var inner in ex.InnerExceptions)\n        {\n            Console.WriteLine($\"- {inner.GetType().Name}: {inner.Message}\");\n        }\n    }\n    catch (Exception ex)  \u002F\u002F 实际 WhenAll 会抛出第一个异常，但其他异常被包装\n    {\n        \u002F\u002F 注意：await Task.WhenAll 会抛出第一个异常\n        \u002F\u002F 但其他异常可以通过 task.Exception 访问\n        Console.WriteLine($\"捕获第一个异常: {ex.Message}\");\n        \n        \u002F\u002F 检查所有任务的异常\n        if (task1.IsFaulted)\n            Console.WriteLine($\"Task1 异常: {task1.Exception?.InnerException?.Message}\");\n        if (task2.IsFaulted)\n            Console.WriteLine($\"Task2 异常: {task2.Exception?.InnerException?.Message}\");\n    }\n}\n\n\u002F\u002F 输出示例（取决于哪个先完成）：\n\u002F\u002F 捕获第一个异常: Task 异常\n\u002F\u002F Task1 异常: Task 异常\n\u002F\u002F Task2 异常: 另一个 Task 异常\n",[152,2676,2677,2682,2686,2691,2696,2700,2704,2709,2713,2718,2723,2727,2731,2736,2740,2745,2750,2754,2759,2763,2768,2773,2777,2782,2786,2791,2796,2800,2805,2809,2813,2818,2822,2827,2832,2837,2841,2846,2851,2856,2861,2866,2870,2874,2878,2883,2888,2893],{"__ignoreMap":150},[155,2678,2679],{"class":157,"line":158},[155,2680,2681],{},"public async Task TaskWithException()\n",[155,2683,2684],{"class":157,"line":164},[155,2685,565],{},[155,2687,2688],{"class":157,"line":170},[155,2689,2690],{},"    await Task.Delay(100);\n",[155,2692,2693],{"class":157,"line":176},[155,2694,2695],{},"    throw new InvalidOperationException(\"Task 异常\");\n",[155,2697,2698],{"class":157,"line":182},[155,2699,661],{},[155,2701,2702],{"class":157,"line":188},[155,2703,517],{"emptyLinePlaceholder":516},[155,2705,2706],{"class":157,"line":194},[155,2707,2708],{},"public async Task AnotherTaskWithException()\n",[155,2710,2711],{"class":157,"line":200},[155,2712,565],{},[155,2714,2715],{"class":157,"line":206},[155,2716,2717],{},"    await Task.Delay(200);\n",[155,2719,2720],{"class":157,"line":347},[155,2721,2722],{},"    throw new ArgumentException(\"另一个 Task 异常\");\n",[155,2724,2725],{"class":157,"line":353},[155,2726,661],{},[155,2728,2729],{"class":157,"line":358},[155,2730,517],{"emptyLinePlaceholder":516},[155,2732,2733],{"class":157,"line":363},[155,2734,2735],{},"public async Task DemonstrateAggregation()\n",[155,2737,2738],{"class":157,"line":369},[155,2739,565],{},[155,2741,2742],{"class":157,"line":375},[155,2743,2744],{},"    var task1 = TaskWithException();\n",[155,2746,2747],{"class":157,"line":381},[155,2748,2749],{},"    var task2 = AnotherTaskWithException();\n",[155,2751,2752],{"class":157,"line":387},[155,2753,339],{},[155,2755,2756],{"class":157,"line":393},[155,2757,2758],{},"    try\n",[155,2760,2761],{"class":157,"line":399},[155,2762,607],{},[155,2764,2765],{"class":157,"line":405},[155,2766,2767],{},"        \u002F\u002F ✅ 所有异常被聚合到一个 AggregateException 中\n",[155,2769,2770],{"class":157,"line":411},[155,2771,2772],{},"        await Task.WhenAll(task1, task2);\n",[155,2774,2775],{"class":157,"line":416},[155,2776,656],{},[155,2778,2779],{"class":157,"line":421},[155,2780,2781],{},"    catch (AggregateException ex)  \u002F\u002F 注意：这里捕获的是 AggregateException\n",[155,2783,2784],{"class":157,"line":426},[155,2785,607],{},[155,2787,2788],{"class":157,"line":432},[155,2789,2790],{},"        Console.WriteLine($\"聚合异常数量: {ex.InnerExceptions.Count}\");\n",[155,2792,2793],{"class":157,"line":438},[155,2794,2795],{},"        foreach (var inner in ex.InnerExceptions)\n",[155,2797,2798],{"class":157,"line":444},[155,2799,1011],{},[155,2801,2802],{"class":157,"line":450},[155,2803,2804],{},"            Console.WriteLine($\"- {inner.GetType().Name}: {inner.Message}\");\n",[155,2806,2807],{"class":157,"line":455},[155,2808,1031],{},[155,2810,2811],{"class":157,"line":461},[155,2812,656],{},[155,2814,2815],{"class":157,"line":467},[155,2816,2817],{},"    catch (Exception ex)  \u002F\u002F 实际 WhenAll 会抛出第一个异常，但其他异常被包装\n",[155,2819,2820],{"class":157,"line":473},[155,2821,607],{},[155,2823,2824],{"class":157,"line":479},[155,2825,2826],{},"        \u002F\u002F 注意：await Task.WhenAll 会抛出第一个异常\n",[155,2828,2829],{"class":157,"line":484},[155,2830,2831],{},"        \u002F\u002F 但其他异常可以通过 task.Exception 访问\n",[155,2833,2834],{"class":157,"line":2058},[155,2835,2836],{},"        Console.WriteLine($\"捕获第一个异常: {ex.Message}\");\n",[155,2838,2839],{"class":157,"line":2064},[155,2840,627],{},[155,2842,2843],{"class":157,"line":2070},[155,2844,2845],{},"        \u002F\u002F 检查所有任务的异常\n",[155,2847,2848],{"class":157,"line":2076},[155,2849,2850],{},"        if (task1.IsFaulted)\n",[155,2852,2853],{"class":157,"line":2082},[155,2854,2855],{},"            Console.WriteLine($\"Task1 异常: {task1.Exception?.InnerException?.Message}\");\n",[155,2857,2858],{"class":157,"line":2088},[155,2859,2860],{},"        if (task2.IsFaulted)\n",[155,2862,2863],{"class":157,"line":2094},[155,2864,2865],{},"            Console.WriteLine($\"Task2 异常: {task2.Exception?.InnerException?.Message}\");\n",[155,2867,2868],{"class":157,"line":2100},[155,2869,656],{},[155,2871,2872],{"class":157,"line":2106},[155,2873,661],{},[155,2875,2876],{"class":157,"line":2111},[155,2877,517],{"emptyLinePlaceholder":516},[155,2879,2880],{"class":157,"line":2116},[155,2881,2882],{},"\u002F\u002F 输出示例（取决于哪个先完成）：\n",[155,2884,2885],{"class":157,"line":2121},[155,2886,2887],{},"\u002F\u002F 捕获第一个异常: Task 异常\n",[155,2889,2890],{"class":157,"line":2127},[155,2891,2892],{},"\u002F\u002F Task1 异常: Task 异常\n",[155,2894,2895],{"class":157,"line":2132},[155,2896,2897],{},"\u002F\u002F Task2 异常: 另一个 Task 异常\n",[14,2899,2900],{},"await是一种语法糖，他可以实现自动解包装，而不需要你手动编写GetAwater().Result();，后者没有前者方便，并且有一定问题。async方法默认是在调用线程上面开始执行的，但是遇到了awit的时候会发生切换线程的事件：",[145,2902,2904],{"className":546,"code":2903,"language":548,"meta":150,"style":150},"public async Task MyMethodAsync()\n{\n    Console.WriteLine($\"1. 开始执行，线程: {Thread.CurrentThread.ManagedThreadId}\");\n    \n    await Task.Delay(1000);  \u002F\u002F 这里可能切换线程\n    \n    Console.WriteLine($\"2. 恢复执行，线程: {Thread.CurrentThread.ManagedThreadId}\");\n}\n\n\u002F\u002F 调用\nConsole.WriteLine($\"主线程: {Thread.CurrentThread.ManagedThreadId}\");\nawait MyMethodAsync();\nConsole.WriteLine($\"完成后主线程: {Thread.CurrentThread.ManagedThreadId}\");\n",[152,2905,2906,2911,2915,2920,2924,2929,2933,2938,2942,2946,2951,2956,2961],{"__ignoreMap":150},[155,2907,2908],{"class":157,"line":158},[155,2909,2910],{},"public async Task MyMethodAsync()\n",[155,2912,2913],{"class":157,"line":164},[155,2914,565],{},[155,2916,2917],{"class":157,"line":170},[155,2918,2919],{},"    Console.WriteLine($\"1. 开始执行，线程: {Thread.CurrentThread.ManagedThreadId}\");\n",[155,2921,2922],{"class":157,"line":176},[155,2923,339],{},[155,2925,2926],{"class":157,"line":182},[155,2927,2928],{},"    await Task.Delay(1000);  \u002F\u002F 这里可能切换线程\n",[155,2930,2931],{"class":157,"line":188},[155,2932,339],{},[155,2934,2935],{"class":157,"line":194},[155,2936,2937],{},"    Console.WriteLine($\"2. 恢复执行，线程: {Thread.CurrentThread.ManagedThreadId}\");\n",[155,2939,2940],{"class":157,"line":200},[155,2941,661],{},[155,2943,2944],{"class":157,"line":206},[155,2945,517],{"emptyLinePlaceholder":516},[155,2947,2948],{"class":157,"line":347},[155,2949,2950],{},"\u002F\u002F 调用\n",[155,2952,2953],{"class":157,"line":353},[155,2954,2955],{},"Console.WriteLine($\"主线程: {Thread.CurrentThread.ManagedThreadId}\");\n",[155,2957,2958],{"class":157,"line":358},[155,2959,2960],{},"await MyMethodAsync();\n",[155,2962,2963],{"class":157,"line":363},[155,2964,2965],{},"Console.WriteLine($\"完成后主线程: {Thread.CurrentThread.ManagedThreadId}\");\n",[14,2967,2968],{},"主线程: 1",[2970,2971,2972,2976],"ol",{},[2973,2974,2975],"li",{},"开始执行，线程: 1",[2973,2977,2978],{},"恢复执行，线程: 1\n完成后主线程: 1",[14,2980,2981],{},"恢复时可能在不同线程，具体可以用下面的图来讲解：",[145,2983,2985],{"className":147,"code":2984,"language":149,"meta":150,"style":150},"graph TB\n    subgraph Call [调用 async 方法]\n        A[调用者线程] --> B[进入 async 方法]\n        B --> C[同步执行到第一个 await]\n    end\n    \n    subgraph Await [遇到 await]\n        C --> D{await 的对象\u003Cbr>是否已完成?}\n        D -->|是| E[继续同步执行]\n        D -->|否| F[方法返回未完成的 Task]\n        F --> G[调用者继续执行]\n    end\n    \n    subgraph Resume [恢复执行]\n        H[异步操作完成] --> I{是否有 SynchronizationContext?}\n        I -->|UI\u002FWPF| J[回到原线程]\n        I -->|ASP.NET| J\n        I -->|Console| K[可能在任意线程池线程]\n    end\n",[152,2986,2987,2991,2996,3001,3006,3010,3014,3019,3024,3029,3034,3039,3043,3047,3052,3057,3062,3067,3072],{"__ignoreMap":150},[155,2988,2989],{"class":157,"line":158},[155,2990,304],{},[155,2992,2993],{"class":157,"line":164},[155,2994,2995],{},"    subgraph Call [调用 async 方法]\n",[155,2997,2998],{"class":157,"line":170},[155,2999,3000],{},"        A[调用者线程] --> B[进入 async 方法]\n",[155,3002,3003],{"class":157,"line":176},[155,3004,3005],{},"        B --> C[同步执行到第一个 await]\n",[155,3007,3008],{"class":157,"line":182},[155,3009,334],{},[155,3011,3012],{"class":157,"line":188},[155,3013,339],{},[155,3015,3016],{"class":157,"line":194},[155,3017,3018],{},"    subgraph Await [遇到 await]\n",[155,3020,3021],{"class":157,"line":200},[155,3022,3023],{},"        C --> D{await 的对象\u003Cbr>是否已完成?}\n",[155,3025,3026],{"class":157,"line":206},[155,3027,3028],{},"        D -->|是| E[继续同步执行]\n",[155,3030,3031],{"class":157,"line":347},[155,3032,3033],{},"        D -->|否| F[方法返回未完成的 Task]\n",[155,3035,3036],{"class":157,"line":353},[155,3037,3038],{},"        F --> G[调用者继续执行]\n",[155,3040,3041],{"class":157,"line":358},[155,3042,334],{},[155,3044,3045],{"class":157,"line":363},[155,3046,339],{},[155,3048,3049],{"class":157,"line":369},[155,3050,3051],{},"    subgraph Resume [恢复执行]\n",[155,3053,3054],{"class":157,"line":375},[155,3055,3056],{},"        H[异步操作完成] --> I{是否有 SynchronizationContext?}\n",[155,3058,3059],{"class":157,"line":381},[155,3060,3061],{},"        I -->|UI\u002FWPF| J[回到原线程]\n",[155,3063,3064],{"class":157,"line":387},[155,3065,3066],{},"        I -->|ASP.NET| J\n",[155,3068,3069],{"class":157,"line":393},[155,3070,3071],{},"        I -->|Console| K[可能在任意线程池线程]\n",[155,3073,3074],{"class":157,"line":399},[155,3075,334],{},[289,3077,3078],{"id":3078},"同步上下文",[14,3080,3081],{},"这是一种管理和协调线程的机制，允许开发者将代码的执行切换到特定的线程，UI框架比如WPF、Winform都是含有这个的，但是控制台程序没有。",[14,3083,3084],{},"我们也可以在Task上面调用ConfigureAwait(true\u002Ffalse)，配置为true的时候，任务通过awit方法结束后会回到原来的线程，如果是false，则不回来。",[289,3086,3087],{"id":3087},"取消令牌",[14,3089,3090],{},"取消令牌涉及了两个很重要的变量，一个叫做CancellationToken，一个叫做 CancellationTokenSource，这些是实现协作式取消的核心机制。",[14,3092,3093],{},"![[Pasted image 20260327223029.png]]",[14,3095,3096],{},"CancellationToken是通过CancellationTokenSource生成的，因此我们需要先获取令牌之后，再传递给异步操作。",[14,3098,3099],{},"await DoWorkAsync(token);\nawait AnotherTaskAsync(token);",[14,3101,3102],{},"本质上是可以把CancellationTokenSource看做是一个生产者端，可以把CancellationToken看作是消费端，Srouce可以通过Cancel()来触发和发出取消信号，来通知所有接收到了Token的任务（消费者）。",[3104,3105,3107],"h4",{"id":3106},"ctscancel和tokeniscancellationrequested","cts.Cancel和Token.IsCancellationRequested",[14,3109,3110],{},"当我们使用cts.Cancel()的时候会向传递了Token的任务发送取消的通知，那么取消的时候接下来我们该如何处理，一般情况下，异步任务就会去判断是否被取消了，如果被取消了那么就不用做接下来的工作了，可以用ThrowIfCancellationRequested();方法去正常抛出异常。",[145,3112,3114],{"className":546,"code":3113,"language":548,"meta":150,"style":150},"Task FooAsync3(CancellationToken cancellationToken)\n{\n    return Task.Run(() =>\n    {\n        if (cancellationToken.IsCancellationRequested) {\n            cancellationToken.ThrowIfCancellationRequested();\n        }\n        while (true)\n        {\n            Thread.Sleep(1000);\n            Console.WriteLine(\"finished\");\n        }\n    });\n}\n",[152,3115,3116,3121,3125,3130,3134,3139,3144,3148,3153,3157,3161,3166,3170,3175],{"__ignoreMap":150},[155,3117,3118],{"class":157,"line":158},[155,3119,3120],{},"Task FooAsync3(CancellationToken cancellationToken)\n",[155,3122,3123],{"class":157,"line":164},[155,3124,565],{},[155,3126,3127],{"class":157,"line":170},[155,3128,3129],{},"    return Task.Run(() =>\n",[155,3131,3132],{"class":157,"line":176},[155,3133,607],{},[155,3135,3136],{"class":157,"line":182},[155,3137,3138],{},"        if (cancellationToken.IsCancellationRequested) {\n",[155,3140,3141],{"class":157,"line":188},[155,3142,3143],{},"            cancellationToken.ThrowIfCancellationRequested();\n",[155,3145,3146],{"class":157,"line":194},[155,3147,1031],{},[155,3149,3150],{"class":157,"line":200},[155,3151,3152],{},"        while (true)\n",[155,3154,3155],{"class":157,"line":206},[155,3156,1011],{},[155,3158,3159],{"class":157,"line":347},[155,3160,2273],{},[155,3162,3163],{"class":157,"line":353},[155,3164,3165],{},"            Console.WriteLine(\"finished\");\n",[155,3167,3168],{"class":157,"line":358},[155,3169,1031],{},[155,3171,3172],{"class":157,"line":363},[155,3173,3174],{},"    });\n",[155,3176,3177],{"class":157,"line":369},[155,3178,661],{},[3104,3180,3181],{"id":3181},"任务取消时候的对策",[14,3183,3184],{},"![[Pasted image 20260329125646.png]]",[14,3186,3187],{},"任务被取消的时候有一些善后工作，比如说释放资源啊，或者等一系列的操作，我们可以在CancellationToken.Register()中注册一个委托来实现。",[14,3189,3190],{},"任务取消我们可以实现提前返回，可以使用Task.FromCanceled()，这个是C#中用于创阿金已取消任务的静态方法。",[145,3192,3194],{"className":546,"code":3193,"language":548,"meta":150,"style":150},"\u002F\u002F 基本用法\nCancellationTokenSource cts = new CancellationTokenSource();\ncts.Cancel();\n\n\u002F\u002F 创建已取消的 Task\nTask cancelledTask = Task.FromCanceled(cts.Token);\n\n\u002F\u002F 或创建已取消的 Task\u003CTResult>\nTask\u003Cstring> cancelledTaskWithResult = Task.FromCanceled\u003Cstring>(cts.Token);\n",[152,3195,3196,3201,3206,3211,3215,3220,3225,3229,3234],{"__ignoreMap":150},[155,3197,3198],{"class":157,"line":158},[155,3199,3200],{},"\u002F\u002F 基本用法\n",[155,3202,3203],{"class":157,"line":164},[155,3204,3205],{},"CancellationTokenSource cts = new CancellationTokenSource();\n",[155,3207,3208],{"class":157,"line":170},[155,3209,3210],{},"cts.Cancel();\n",[155,3212,3213],{"class":157,"line":176},[155,3214,517],{"emptyLinePlaceholder":516},[155,3216,3217],{"class":157,"line":182},[155,3218,3219],{},"\u002F\u002F 创建已取消的 Task\n",[155,3221,3222],{"class":157,"line":188},[155,3223,3224],{},"Task cancelledTask = Task.FromCanceled(cts.Token);\n",[155,3226,3227],{"class":157,"line":194},[155,3228,517],{"emptyLinePlaceholder":516},[155,3230,3231],{"class":157,"line":200},[155,3232,3233],{},"\u002F\u002F 或创建已取消的 Task\u003CTResult>\n",[155,3235,3236],{"class":157,"line":206},[155,3237,3238],{},"Task\u003Cstring> cancelledTaskWithResult = Task.FromCanceled\u003Cstring>(cts.Token);\n",[145,3240,3242],{"className":546,"code":3241,"language":548,"meta":150,"style":150},"public async Task\u003Cstring> GetDataAsync(CancellationToken cancellationToken)\n{\n    if (cancellationToken.IsCancellationRequested)\n    {\n        \u002F\u002F 如果已经取消，直接返回已取消的任务\n        return await Task.FromCanceled\u003Cstring>(cancellationToken);\n    }\n    \n    \u002F\u002F 实际执行异步操作\n    return await FetchDataAsync(cancellationToken);\n}\n",[152,3243,3244,3249,3253,3258,3262,3267,3272,3276,3280,3285,3290],{"__ignoreMap":150},[155,3245,3246],{"class":157,"line":158},[155,3247,3248],{},"public async Task\u003Cstring> GetDataAsync(CancellationToken cancellationToken)\n",[155,3250,3251],{"class":157,"line":164},[155,3252,565],{},[155,3254,3255],{"class":157,"line":170},[155,3256,3257],{},"    if (cancellationToken.IsCancellationRequested)\n",[155,3259,3260],{"class":157,"line":176},[155,3261,607],{},[155,3263,3264],{"class":157,"line":182},[155,3265,3266],{},"        \u002F\u002F 如果已经取消，直接返回已取消的任务\n",[155,3268,3269],{"class":157,"line":188},[155,3270,3271],{},"        return await Task.FromCanceled\u003Cstring>(cancellationToken);\n",[155,3273,3274],{"class":157,"line":194},[155,3275,656],{},[155,3277,3278],{"class":157,"line":200},[155,3279,339],{},[155,3281,3282],{"class":157,"line":206},[155,3283,3284],{},"    \u002F\u002F 实际执行异步操作\n",[155,3286,3287],{"class":157,"line":347},[155,3288,3289],{},"    return await FetchDataAsync(cancellationToken);\n",[155,3291,3292],{"class":157,"line":353},[155,3293,661],{},[14,3295,3296],{},"与它相对的是Task.FromResult()；用于创建已经完成的任务。",[14,3298,3299],{},"当操作取消的时候，我们也可以在try-catch里面捕获到，可以用OperationCanceledException。除此以外，我们可以在Task.Run()中也传入Token，这样可以实现预先检查的目的，也就是如果token已经被取消过了，那么任务也就不会进行了。",[145,3301,3303],{"className":546,"code":3302,"language":548,"meta":150,"style":150},"try\n{\n    await Task.Run(() => {\n    for(int i =0; i\u003C1000;i++){\n    if(token.IsCancellationRequested)token.ThrowIfCancellationRequested();\n        Thread.Sleep(1000);\n        Console.WriteLine(\"pooling..\");\n    }\n    },token);\n}catch(OperationCanceledException e){\n    Console.WriteLine(e);\n}finally{\n    _cts.Dispose();\n}try\n{\n    await Task.Run(() => {\n    for(int i =0; i\u003C1000;i++){\n    if(token.IsCancellationRequested)token.ThrowIfCancellationRequested();\n        Thread.Sleep(1000);\n        Console.WriteLine(\"pooling..\");\n    }\n    },token);\n}catch(OperationCanceledException e){\n    Console.WriteLine(e);\n}finally{\n    _cts.Dispose();\n}\n",[152,3304,3305,3309,3313,3318,3323,3328,3333,3338,3342,3347,3352,3357,3362,3367,3372,3376,3380,3384,3388,3392,3396,3400,3404,3408,3412,3416,3420],{"__ignoreMap":150},[155,3306,3307],{"class":157,"line":158},[155,3308,1090],{},[155,3310,3311],{"class":157,"line":164},[155,3312,565],{},[155,3314,3315],{"class":157,"line":170},[155,3316,3317],{},"    await Task.Run(() => {\n",[155,3319,3320],{"class":157,"line":176},[155,3321,3322],{},"    for(int i =0; i\u003C1000;i++){\n",[155,3324,3325],{"class":157,"line":182},[155,3326,3327],{},"    if(token.IsCancellationRequested)token.ThrowIfCancellationRequested();\n",[155,3329,3330],{"class":157,"line":188},[155,3331,3332],{},"        Thread.Sleep(1000);\n",[155,3334,3335],{"class":157,"line":194},[155,3336,3337],{},"        Console.WriteLine(\"pooling..\");\n",[155,3339,3340],{"class":157,"line":200},[155,3341,656],{},[155,3343,3344],{"class":157,"line":206},[155,3345,3346],{},"    },token);\n",[155,3348,3349],{"class":157,"line":347},[155,3350,3351],{},"}catch(OperationCanceledException e){\n",[155,3353,3354],{"class":157,"line":353},[155,3355,3356],{},"    Console.WriteLine(e);\n",[155,3358,3359],{"class":157,"line":358},[155,3360,3361],{},"}finally{\n",[155,3363,3364],{"class":157,"line":363},[155,3365,3366],{},"    _cts.Dispose();\n",[155,3368,3369],{"class":157,"line":369},[155,3370,3371],{},"}try\n",[155,3373,3374],{"class":157,"line":375},[155,3375,565],{},[155,3377,3378],{"class":157,"line":381},[155,3379,3317],{},[155,3381,3382],{"class":157,"line":387},[155,3383,3322],{},[155,3385,3386],{"class":157,"line":393},[155,3387,3327],{},[155,3389,3390],{"class":157,"line":399},[155,3391,3332],{},[155,3393,3394],{"class":157,"line":405},[155,3395,3337],{},[155,3397,3398],{"class":157,"line":411},[155,3399,656],{},[155,3401,3402],{"class":157,"line":416},[155,3403,3346],{},[155,3405,3406],{"class":157,"line":421},[155,3407,3351],{},[155,3409,3410],{"class":157,"line":426},[155,3411,3356],{},[155,3413,3414],{"class":157,"line":432},[155,3415,3361],{},[155,3417,3418],{"class":157,"line":438},[155,3419,3366],{},[155,3421,3422],{"class":157,"line":444},[155,3423,661],{},[3104,3425,3426],{"id":3426},"超时机制",[14,3428,3429],{},"对于我们之前提到的Thread，他有Join方法的重载，这个重载可以传入时间参数，你可以通过判断!thread.Join(TimeSpan....)的方式来使用线程的打断。",[145,3431,3436],{"className":3432,"code":3434,"language":3435},[3433],"language-text","\u002F\u002Fvar thread = new Thread();\n\u002F\u002Fthread.Start();\n\u002F\u002Fif (!thread.Join(TimeSpan.FromMilliseconds(2000))){\n\u002F\u002F  thread.Interrupt();\n\u002F\u002F}\n\u002F\u002FConsole.WriteLine(\"done\");\n","text",[152,3437,3434],{"__ignoreMap":150},[14,3439,3440],{},"或者是使用Task来进行打断，Task有叫做WhenAny的方法，我们可以传入多个task方法进去，一个是我们的任务，一个是Task.Delay方法，这样只要超时了之后就会返回，我们就可以实现。",[145,3442,3444],{"className":546,"code":3443,"language":548,"meta":150,"style":150},"var cts = new CancellationTokenSource();\nvar task = FooAsync(cts.Token);\nvar completedTask = await Task.WhenAny(task, Task.Delay(2000));\nif (completedTask != task) {\n    \u002F\u002Fcts.Cancel();\n    \u002F\u002Fawait task;\n    Console.WriteLine(\"Timeout\");\n}\nConsole.WriteLine(\"Done\");\n\nasync Task FooAsync(CancellationToken token){\n    try{\n        Console.WriteLine(\"foo start\");\n        await Task.Delay(5000, token);\n        Console.WriteLine(\"end\");\n    }catch(OperationCanceledException){\n        Console.WriteLine(\"interrupted..\");\n    }\n}\n",[152,3445,3446,3451,3456,3461,3466,3471,3476,3481,3485,3490,3494,3499,3504,3509,3514,3519,3524,3529,3533],{"__ignoreMap":150},[155,3447,3448],{"class":157,"line":158},[155,3449,3450],{},"var cts = new CancellationTokenSource();\n",[155,3452,3453],{"class":157,"line":164},[155,3454,3455],{},"var task = FooAsync(cts.Token);\n",[155,3457,3458],{"class":157,"line":170},[155,3459,3460],{},"var completedTask = await Task.WhenAny(task, Task.Delay(2000));\n",[155,3462,3463],{"class":157,"line":176},[155,3464,3465],{},"if (completedTask != task) {\n",[155,3467,3468],{"class":157,"line":182},[155,3469,3470],{},"    \u002F\u002Fcts.Cancel();\n",[155,3472,3473],{"class":157,"line":188},[155,3474,3475],{},"    \u002F\u002Fawait task;\n",[155,3477,3478],{"class":157,"line":194},[155,3479,3480],{},"    Console.WriteLine(\"Timeout\");\n",[155,3482,3483],{"class":157,"line":200},[155,3484,661],{},[155,3486,3487],{"class":157,"line":206},[155,3488,3489],{},"Console.WriteLine(\"Done\");\n",[155,3491,3492],{"class":157,"line":347},[155,3493,517],{"emptyLinePlaceholder":516},[155,3495,3496],{"class":157,"line":353},[155,3497,3498],{},"async Task FooAsync(CancellationToken token){\n",[155,3500,3501],{"class":157,"line":358},[155,3502,3503],{},"    try{\n",[155,3505,3506],{"class":157,"line":363},[155,3507,3508],{},"        Console.WriteLine(\"foo start\");\n",[155,3510,3511],{"class":157,"line":369},[155,3512,3513],{},"        await Task.Delay(5000, token);\n",[155,3515,3516],{"class":157,"line":375},[155,3517,3518],{},"        Console.WriteLine(\"end\");\n",[155,3520,3521],{"class":157,"line":381},[155,3522,3523],{},"    }catch(OperationCanceledException){\n",[155,3525,3526],{"class":157,"line":387},[155,3527,3528],{},"        Console.WriteLine(\"interrupted..\");\n",[155,3530,3531],{"class":157,"line":393},[155,3532,656],{},[155,3534,3535],{"class":157,"line":399},[155,3536,661],{},[14,3538,3539],{},"这个是老的方法，除此以外有一个扩展方法:",[145,3541,3543],{"className":546,"code":3542,"language":548,"meta":150,"style":150},"static class AsyncExtensions{\n    public static async Task TimeoutAfter(this Task task, TimeSpan timeout){\n        using var cts = new CancellationTokenSource();\n        var completedTask = await Task.WhenAny(task, Task.Delay(timeout, cts.Token));\n        if (completedTask != task){\n            cts.Cancel();\n            throw new TimeoutException();\n        }\n        await task;\n    }\n}\n",[152,3544,3545,3550,3555,3560,3565,3570,3575,3580,3584,3589,3593],{"__ignoreMap":150},[155,3546,3547],{"class":157,"line":158},[155,3548,3549],{},"static class AsyncExtensions{\n",[155,3551,3552],{"class":157,"line":164},[155,3553,3554],{},"    public static async Task TimeoutAfter(this Task task, TimeSpan timeout){\n",[155,3556,3557],{"class":157,"line":170},[155,3558,3559],{},"        using var cts = new CancellationTokenSource();\n",[155,3561,3562],{"class":157,"line":176},[155,3563,3564],{},"        var completedTask = await Task.WhenAny(task, Task.Delay(timeout, cts.Token));\n",[155,3566,3567],{"class":157,"line":182},[155,3568,3569],{},"        if (completedTask != task){\n",[155,3571,3572],{"class":157,"line":188},[155,3573,3574],{},"            cts.Cancel();\n",[155,3576,3577],{"class":157,"line":194},[155,3578,3579],{},"            throw new TimeoutException();\n",[155,3581,3582],{"class":157,"line":200},[155,3583,1031],{},[155,3585,3586],{"class":157,"line":206},[155,3587,3588],{},"        await task;\n",[155,3590,3591],{"class":157,"line":347},[155,3592,656],{},[155,3594,3595],{"class":157,"line":353},[155,3596,661],{},[14,3598,3599],{},"这样也可以实现，只要返回的不是task，那么就会抛出超时异常。",[289,3601,3602],{"id":3602},"同步机制中的并发集合",[14,3604,3605],{},"这里面主要有ConcurrentBag、Stack、Queue以及BlockingCollection，主要是处理多线程中的通讯工作的。 从名字也可以看出来，BlockingCollection有阻塞，这个在异步是不能使用的。",[14,3607,3608],{},"但是C#给我提供了一种原生的集合，叫做Channel，可以帮助我们实现异步的通信。",[3104,3610,3612],{"id":3611},"blockingcollection","BlockingCollection",[14,3614,3615,3616,3619,3620,3623],{},"ConcurrentQueue是一个队列，我们可以在里面通过生产者和消费者来实现多线程的通信，并且这个是线程安全的，我们不需要考虑细节。而BlockingCollection是一个.net的线程安全的阻塞合集，它提供了",[18,3617,3618],{},"阻塞和限界","功能，非常适合",[18,3621,3622],{},"生产者-消费者","模式：",[145,3625,3627],{"className":546,"code":3626,"language":548,"meta":150,"style":150},"using var queue = new BlockingCollection\u003CMessage>(new ConcurrentQueue\u003CMessage>());\nvar sender = new Thread(SendMessageThread);\nvar receiver = new Thread(ReceiveMessageThread);\nsender.Start(1);\nreceiver.Start(2);\nsender.Join();\nThread.Sleep(100);\nreceiver.Interrupt();\nreceiver.Join();\nConsole.WriteLine(\"exit..\");\nConsole.ReadKey();\nvoid SendMessageThread(object? arg){\n    int id = (int)arg!;\n    for(int i=1;i\u003C=20;i++){\n        queue.Add(new Message(id, i.ToString()));\n        Console.WriteLine($\"thread{id} sent {i}\");\n        Thread.Sleep(100);\n    }\n}\n\nvoid ReceiveMessageThread(object? id){\n    try{\n        while (true)\n        {\n            var message = queue.Take();\n            Console.WriteLine($\"thread {id} received {message.Content} from {message.FromId}\");\n        }\n    }\n    catch (ThreadInterruptedException)\n    {\n        Console.WriteLine($\"therad {id} interrupted\");\n    }\n}\n\nrecord Message(int FromId, string Content);\n",[152,3628,3629,3634,3639,3644,3649,3654,3659,3664,3669,3674,3679,3684,3689,3694,3699,3704,3709,3714,3718,3722,3726,3731,3735,3739,3743,3748,3753,3757,3761,3766,3770,3775,3779,3783,3787],{"__ignoreMap":150},[155,3630,3631],{"class":157,"line":158},[155,3632,3633],{},"using var queue = new BlockingCollection\u003CMessage>(new ConcurrentQueue\u003CMessage>());\n",[155,3635,3636],{"class":157,"line":164},[155,3637,3638],{},"var sender = new Thread(SendMessageThread);\n",[155,3640,3641],{"class":157,"line":170},[155,3642,3643],{},"var receiver = new Thread(ReceiveMessageThread);\n",[155,3645,3646],{"class":157,"line":176},[155,3647,3648],{},"sender.Start(1);\n",[155,3650,3651],{"class":157,"line":182},[155,3652,3653],{},"receiver.Start(2);\n",[155,3655,3656],{"class":157,"line":188},[155,3657,3658],{},"sender.Join();\n",[155,3660,3661],{"class":157,"line":194},[155,3662,3663],{},"Thread.Sleep(100);\n",[155,3665,3666],{"class":157,"line":200},[155,3667,3668],{},"receiver.Interrupt();\n",[155,3670,3671],{"class":157,"line":206},[155,3672,3673],{},"receiver.Join();\n",[155,3675,3676],{"class":157,"line":347},[155,3677,3678],{},"Console.WriteLine(\"exit..\");\n",[155,3680,3681],{"class":157,"line":353},[155,3682,3683],{},"Console.ReadKey();\n",[155,3685,3686],{"class":157,"line":358},[155,3687,3688],{},"void SendMessageThread(object? arg){\n",[155,3690,3691],{"class":157,"line":363},[155,3692,3693],{},"    int id = (int)arg!;\n",[155,3695,3696],{"class":157,"line":369},[155,3697,3698],{},"    for(int i=1;i\u003C=20;i++){\n",[155,3700,3701],{"class":157,"line":375},[155,3702,3703],{},"        queue.Add(new Message(id, i.ToString()));\n",[155,3705,3706],{"class":157,"line":381},[155,3707,3708],{},"        Console.WriteLine($\"thread{id} sent {i}\");\n",[155,3710,3711],{"class":157,"line":387},[155,3712,3713],{},"        Thread.Sleep(100);\n",[155,3715,3716],{"class":157,"line":393},[155,3717,656],{},[155,3719,3720],{"class":157,"line":399},[155,3721,661],{},[155,3723,3724],{"class":157,"line":405},[155,3725,517],{"emptyLinePlaceholder":516},[155,3727,3728],{"class":157,"line":411},[155,3729,3730],{},"void ReceiveMessageThread(object? id){\n",[155,3732,3733],{"class":157,"line":416},[155,3734,3503],{},[155,3736,3737],{"class":157,"line":421},[155,3738,3152],{},[155,3740,3741],{"class":157,"line":426},[155,3742,1011],{},[155,3744,3745],{"class":157,"line":432},[155,3746,3747],{},"            var message = queue.Take();\n",[155,3749,3750],{"class":157,"line":438},[155,3751,3752],{},"            Console.WriteLine($\"thread {id} received {message.Content} from {message.FromId}\");\n",[155,3754,3755],{"class":157,"line":444},[155,3756,1031],{},[155,3758,3759],{"class":157,"line":450},[155,3760,656],{},[155,3762,3763],{"class":157,"line":455},[155,3764,3765],{},"    catch (ThreadInterruptedException)\n",[155,3767,3768],{"class":157,"line":461},[155,3769,607],{},[155,3771,3772],{"class":157,"line":467},[155,3773,3774],{},"        Console.WriteLine($\"therad {id} interrupted\");\n",[155,3776,3777],{"class":157,"line":473},[155,3778,656],{},[155,3780,3781],{"class":157,"line":479},[155,3782,661],{},[155,3784,3785],{"class":157,"line":484},[155,3786,517],{"emptyLinePlaceholder":516},[155,3788,3789],{"class":157,"line":2058},[155,3790,3791],{},"record Message(int FromId, string Content);\n",[14,3793,3794],{},"这样的话，sender就可以发送消息，receiver就可以接受到消息，我们就完成了多线程的通信了。",[14,3796,3797],{},"限界：",[145,3799,3801],{"className":546,"code":3800,"language":548,"meta":150,"style":150},"\u002F\u002F 容量为3的阻塞队列\nusing var bc = new BlockingCollection\u003Cint>(3);\n\nTask producer = Task.Run(() =>\n{\n    for (int i = 1; i \u003C= 10; i++)\n    {\n        Console.WriteLine($\"尝试生产: {i}\");\n        bc.Add(i);  \u002F\u002F 当队列满时会阻塞\n        Console.WriteLine($\"  生产成功: {i}\");\n    }\n    bc.CompleteAdding();\n});\n\nTask consumer = Task.Run(async () =>\n{\n    foreach (var item in bc.GetConsumingEnumerable())\n    {\n        Console.WriteLine($\"消费: {item}\");\n        await Task.Delay(500);  \u002F\u002F 慢消费\n    }\n});\n\nawait Task.WhenAll(producer, consumer);\n\u002F\u002F 输出会显示生产者在队列满时阻塞，等待消费者取走\n",[152,3802,3803,3808,3813,3817,3822,3826,3831,3835,3840,3845,3850,3854,3859,3864,3868,3873,3877,3882,3886,3891,3896,3900,3904,3908,3913],{"__ignoreMap":150},[155,3804,3805],{"class":157,"line":158},[155,3806,3807],{},"\u002F\u002F 容量为3的阻塞队列\n",[155,3809,3810],{"class":157,"line":164},[155,3811,3812],{},"using var bc = new BlockingCollection\u003Cint>(3);\n",[155,3814,3815],{"class":157,"line":170},[155,3816,517],{"emptyLinePlaceholder":516},[155,3818,3819],{"class":157,"line":176},[155,3820,3821],{},"Task producer = Task.Run(() =>\n",[155,3823,3824],{"class":157,"line":182},[155,3825,565],{},[155,3827,3828],{"class":157,"line":188},[155,3829,3830],{},"    for (int i = 1; i \u003C= 10; i++)\n",[155,3832,3833],{"class":157,"line":194},[155,3834,607],{},[155,3836,3837],{"class":157,"line":200},[155,3838,3839],{},"        Console.WriteLine($\"尝试生产: {i}\");\n",[155,3841,3842],{"class":157,"line":206},[155,3843,3844],{},"        bc.Add(i);  \u002F\u002F 当队列满时会阻塞\n",[155,3846,3847],{"class":157,"line":347},[155,3848,3849],{},"        Console.WriteLine($\"  生产成功: {i}\");\n",[155,3851,3852],{"class":157,"line":353},[155,3853,656],{},[155,3855,3856],{"class":157,"line":358},[155,3857,3858],{},"    bc.CompleteAdding();\n",[155,3860,3861],{"class":157,"line":363},[155,3862,3863],{},"});\n",[155,3865,3866],{"class":157,"line":369},[155,3867,517],{"emptyLinePlaceholder":516},[155,3869,3870],{"class":157,"line":375},[155,3871,3872],{},"Task consumer = Task.Run(async () =>\n",[155,3874,3875],{"class":157,"line":381},[155,3876,565],{},[155,3878,3879],{"class":157,"line":387},[155,3880,3881],{},"    foreach (var item in bc.GetConsumingEnumerable())\n",[155,3883,3884],{"class":157,"line":393},[155,3885,607],{},[155,3887,3888],{"class":157,"line":399},[155,3889,3890],{},"        Console.WriteLine($\"消费: {item}\");\n",[155,3892,3893],{"class":157,"line":405},[155,3894,3895],{},"        await Task.Delay(500);  \u002F\u002F 慢消费\n",[155,3897,3898],{"class":157,"line":411},[155,3899,656],{},[155,3901,3902],{"class":157,"line":416},[155,3903,3863],{},[155,3905,3906],{"class":157,"line":421},[155,3907,517],{"emptyLinePlaceholder":516},[155,3909,3910],{"class":157,"line":426},[155,3911,3912],{},"await Task.WhenAll(producer, consumer);\n",[155,3914,3915],{"class":157,"line":432},[155,3916,3917],{},"\u002F\u002F 输出会显示生产者在队列满时阻塞，等待消费者取走\n",[14,3919,3920],{},"多生产者-多消费者：",[145,3922,3924],{"className":546,"code":3923,"language":548,"meta":150,"style":150},"using var bc = new BlockingCollection\u003Cint>();\n\n\u002F\u002F 多个生产者\nvar producers = Enumerable.Range(1, 3).Select(id => Task.Run(() =>\n{\n    for (int i = 1; i \u003C= 5; i++)\n    {\n        bc.Add(i);\n        Console.WriteLine($\"生产者{id} 生产: {i}\");\n        Thread.Sleep(50);\n    }\n}));\n\n\u002F\u002F 多个消费者\nvar consumers = Enumerable.Range(1, 2).Select(id => Task.Run(() =>\n{\n    foreach (var item in bc.GetConsumingEnumerable())\n    {\n        Console.WriteLine($\"消费者{id} 消费: {item}\");\n        Thread.Sleep(100);\n    }\n}));\n\nawait Task.WhenAll(producers);\nbc.CompleteAdding();  \u002F\u002F 所有生产者完成后标记完成\nawait Task.WhenAll(consumers);\n",[152,3925,3926,3931,3935,3940,3945,3949,3954,3958,3963,3968,3973,3977,3982,3986,3991,3996,4000,4004,4008,4013,4017,4021,4025,4029,4034,4039],{"__ignoreMap":150},[155,3927,3928],{"class":157,"line":158},[155,3929,3930],{},"using var bc = new BlockingCollection\u003Cint>();\n",[155,3932,3933],{"class":157,"line":164},[155,3934,517],{"emptyLinePlaceholder":516},[155,3936,3937],{"class":157,"line":170},[155,3938,3939],{},"\u002F\u002F 多个生产者\n",[155,3941,3942],{"class":157,"line":176},[155,3943,3944],{},"var producers = Enumerable.Range(1, 3).Select(id => Task.Run(() =>\n",[155,3946,3947],{"class":157,"line":182},[155,3948,565],{},[155,3950,3951],{"class":157,"line":188},[155,3952,3953],{},"    for (int i = 1; i \u003C= 5; i++)\n",[155,3955,3956],{"class":157,"line":194},[155,3957,607],{},[155,3959,3960],{"class":157,"line":200},[155,3961,3962],{},"        bc.Add(i);\n",[155,3964,3965],{"class":157,"line":206},[155,3966,3967],{},"        Console.WriteLine($\"生产者{id} 生产: {i}\");\n",[155,3969,3970],{"class":157,"line":347},[155,3971,3972],{},"        Thread.Sleep(50);\n",[155,3974,3975],{"class":157,"line":353},[155,3976,656],{},[155,3978,3979],{"class":157,"line":358},[155,3980,3981],{},"}));\n",[155,3983,3984],{"class":157,"line":363},[155,3985,517],{"emptyLinePlaceholder":516},[155,3987,3988],{"class":157,"line":369},[155,3989,3990],{},"\u002F\u002F 多个消费者\n",[155,3992,3993],{"class":157,"line":375},[155,3994,3995],{},"var consumers = Enumerable.Range(1, 2).Select(id => Task.Run(() =>\n",[155,3997,3998],{"class":157,"line":381},[155,3999,565],{},[155,4001,4002],{"class":157,"line":387},[155,4003,3881],{},[155,4005,4006],{"class":157,"line":393},[155,4007,607],{},[155,4009,4010],{"class":157,"line":399},[155,4011,4012],{},"        Console.WriteLine($\"消费者{id} 消费: {item}\");\n",[155,4014,4015],{"class":157,"line":405},[155,4016,3713],{},[155,4018,4019],{"class":157,"line":411},[155,4020,656],{},[155,4022,4023],{"class":157,"line":416},[155,4024,3981],{},[155,4026,4027],{"class":157,"line":421},[155,4028,517],{"emptyLinePlaceholder":516},[155,4030,4031],{"class":157,"line":426},[155,4032,4033],{},"await Task.WhenAll(producers);\n",[155,4035,4036],{"class":157,"line":432},[155,4037,4038],{},"bc.CompleteAdding();  \u002F\u002F 所有生产者完成后标记完成\n",[155,4040,4041],{"class":157,"line":438},[155,4042,4043],{},"await Task.WhenAll(consumers);\n",[34,4045,4046,4058],{},[37,4047,4048],{},[40,4049,4050,4053,4055],{},[43,4051,4052],{},"特性",[43,4054,3612],{},[43,4056,4057],{},"ConcurrentQueue",[53,4059,4060,4071,4082,4093,4104],{},[40,4061,4062,4065,4068],{},[58,4063,4064],{},"阻塞操作",[58,4066,4067],{},"✅ Add\u002FTake 会阻塞",[58,4069,4070],{},"❌ 立即返回",[40,4072,4073,4076,4079],{},[58,4074,4075],{},"限界",[58,4077,4078],{},"✅ 可设置容量",[58,4080,4081],{},"❌ 无限制",[40,4083,4084,4087,4090],{},[58,4085,4086],{},"完成通知",[58,4088,4089],{},"✅ CompleteAdding",[58,4091,4092],{},"❌ 无",[40,4094,4095,4098,4101],{},[58,4096,4097],{},"生产-消费模式",[58,4099,4100],{},"✅ 完美支持",[58,4102,4103],{},"⚠️ 需要轮询",[40,4105,4106,4109,4112],{},[58,4107,4108],{},"性能",[58,4110,4111],{},"略低（有阻塞开销）",[58,4113,4114],{},"高",[3104,4116,4118],{"id":4117},"channel","Channel",[14,4120,4121],{},"而它呢，和我们刚才提到的Concurrentqueue有些相似，还带了个信号量。[[",[145,4123,4125],{"className":546,"code":4124,"language":548,"meta":150,"style":150},"\u002F\u002Fvar queue = new BlockingCollection\u003CMessage>(new ConcurrentQueue\u003CMessage>());\n\u002F\u002Fvar options = new BoundedChannelOptions(10){\n\u002F\u002F  FullMode = BoundedChannelFullMode.Wait, \u002F\u002F 满的时候：等待\n\u002F\u002F  SingleReader = true, \u002F\u002F 单读者模式\n\u002F\u002F  SingleWriter = true \u002F\u002F 单写者模式\n\u002F\u002F};\nvar channel = Channel.CreateUnbounded\u003CMessage>(\n\n);\nvar sender = new Thread(SendMessageThread);\nvar receiver = new Thread(ReceiveMessageThread);\nsender.Start(1);\nreceiver.Start(2);\nsender.Join();\nThread.Sleep(100);\nreceiver.Interrupt();\nreceiver.Join();\nConsole.WriteLine(\"exit..\");\nConsole.ReadKey();\nvoid SendMessageThread(object? arg){\n    int id = (int)arg!;\n    for(int i=1;i\u003C=20;i++){\n        \u002F\u002Fqueue.Add(new Message(id, i.ToString()));\n        if(channel.Writer.TryWrite(new Message(id, i.ToString())))\n        Console.WriteLine($\"thread{id} sent {i}\");\n        Thread.Sleep(100);\n    }\n}\n\nvoid ReceiveMessageThread(object? id){\n    try{\n        while (true)\n        {\n            \u002F\u002Fvar message = queue.Take();\n            if(channel.Reader.TryRead(out var message))\n            Console.WriteLine($\"thread {id} received {message.Content} from {message.FromId}\");\n        }\n    }\n    catch (ThreadInterruptedException)\n    {\n        Console.WriteLine($\"therad {id} interrupted\");\n    }\n}\n\nrecord Message(int FromId, string Content);\n",[152,4126,4127,4132,4137,4145,4153,4161,4166,4171,4175,4180,4184,4188,4192,4196,4200,4204,4208,4212,4216,4220,4224,4228,4232,4237,4242,4246,4250,4254,4258,4262,4266,4270,4274,4278,4283,4288,4292,4296,4300,4304,4308,4312,4316,4320,4324],{"__ignoreMap":150},[155,4128,4129],{"class":157,"line":158},[155,4130,4131],{},"\u002F\u002Fvar queue = new BlockingCollection\u003CMessage>(new ConcurrentQueue\u003CMessage>());\n",[155,4133,4134],{"class":157,"line":164},[155,4135,4136],{},"\u002F\u002Fvar options = new BoundedChannelOptions(10){\n",[155,4138,4139,4142],{"class":157,"line":170},[155,4140,4141],{},"\u002F\u002F  FullMode = BoundedChannelFullMode.Wait,",[155,4143,4144],{}," \u002F\u002F 满的时候：等待\n",[155,4146,4147,4150],{"class":157,"line":176},[155,4148,4149],{},"\u002F\u002F  SingleReader = true,",[155,4151,4152],{}," \u002F\u002F 单读者模式\n",[155,4154,4155,4158],{"class":157,"line":182},[155,4156,4157],{},"\u002F\u002F  SingleWriter = true",[155,4159,4160],{}," \u002F\u002F 单写者模式\n",[155,4162,4163],{"class":157,"line":188},[155,4164,4165],{},"\u002F\u002F};\n",[155,4167,4168],{"class":157,"line":194},[155,4169,4170],{},"var channel = Channel.CreateUnbounded\u003CMessage>(\n",[155,4172,4173],{"class":157,"line":200},[155,4174,517],{"emptyLinePlaceholder":516},[155,4176,4177],{"class":157,"line":206},[155,4178,4179],{},");\n",[155,4181,4182],{"class":157,"line":347},[155,4183,3638],{},[155,4185,4186],{"class":157,"line":353},[155,4187,3643],{},[155,4189,4190],{"class":157,"line":358},[155,4191,3648],{},[155,4193,4194],{"class":157,"line":363},[155,4195,3653],{},[155,4197,4198],{"class":157,"line":369},[155,4199,3658],{},[155,4201,4202],{"class":157,"line":375},[155,4203,3663],{},[155,4205,4206],{"class":157,"line":381},[155,4207,3668],{},[155,4209,4210],{"class":157,"line":387},[155,4211,3673],{},[155,4213,4214],{"class":157,"line":393},[155,4215,3678],{},[155,4217,4218],{"class":157,"line":399},[155,4219,3683],{},[155,4221,4222],{"class":157,"line":405},[155,4223,3688],{},[155,4225,4226],{"class":157,"line":411},[155,4227,3693],{},[155,4229,4230],{"class":157,"line":416},[155,4231,3698],{},[155,4233,4234],{"class":157,"line":421},[155,4235,4236],{},"        \u002F\u002Fqueue.Add(new Message(id, i.ToString()));\n",[155,4238,4239],{"class":157,"line":426},[155,4240,4241],{},"        if(channel.Writer.TryWrite(new Message(id, i.ToString())))\n",[155,4243,4244],{"class":157,"line":432},[155,4245,3708],{},[155,4247,4248],{"class":157,"line":438},[155,4249,3713],{},[155,4251,4252],{"class":157,"line":444},[155,4253,656],{},[155,4255,4256],{"class":157,"line":450},[155,4257,661],{},[155,4259,4260],{"class":157,"line":455},[155,4261,517],{"emptyLinePlaceholder":516},[155,4263,4264],{"class":157,"line":461},[155,4265,3730],{},[155,4267,4268],{"class":157,"line":467},[155,4269,3503],{},[155,4271,4272],{"class":157,"line":473},[155,4273,3152],{},[155,4275,4276],{"class":157,"line":479},[155,4277,1011],{},[155,4279,4280],{"class":157,"line":484},[155,4281,4282],{},"            \u002F\u002Fvar message = queue.Take();\n",[155,4284,4285],{"class":157,"line":2058},[155,4286,4287],{},"            if(channel.Reader.TryRead(out var message))\n",[155,4289,4290],{"class":157,"line":2064},[155,4291,3752],{},[155,4293,4294],{"class":157,"line":2070},[155,4295,1031],{},[155,4297,4298],{"class":157,"line":2076},[155,4299,656],{},[155,4301,4302],{"class":157,"line":2082},[155,4303,3765],{},[155,4305,4306],{"class":157,"line":2088},[155,4307,607],{},[155,4309,4310],{"class":157,"line":2094},[155,4311,3774],{},[155,4313,4314],{"class":157,"line":2100},[155,4315,656],{},[155,4317,4318],{"class":157,"line":2106},[155,4319,661],{},[155,4321,4322],{"class":157,"line":2111},[155,4323,517],{"emptyLinePlaceholder":516},[155,4325,4326],{"class":157,"line":2116},[155,4327,3791],{},[14,4329,4330],{},"我们可以改为Task异步类型的，也可以实现，并且channel里面也肯定是实现了异步的，我们可以直接使用Channel对象携带的writer和reader，它们都有异步方法，可以写入和读取。",[145,4332,4334],{"className":546,"code":4333,"language":548,"meta":150,"style":150},"\u002F\u002Fvar queue = new BlockingCollection\u003CMessage>(new ConcurrentQueue\u003CMessage>());\n\u002F\u002Fvar options = new BoundedChannelOptions(10){\n\u002F\u002F  FullMode = BoundedChannelFullMode.Wait, \u002F\u002F 满的时候：等待\n\u002F\u002F  SingleReader = true, \u002F\u002F 单读者模式\n\u002F\u002F  SingleWriter = true \u002F\u002F 单写者模式\n\u002F\u002F};\nvar channel = Channel.CreateUnbounded\u003CMessage>(\n);\nusing var cts = new CancellationTokenSource();\nvar token = cts.Token;\nvar sender = SendMessageThreadAsync(channel.Writer, 1);\nvar receiver = ReceiveMessageThreadAsync(channel.Reader, 2, token);\nawait sender;\nawait Task.Delay(100);\nawait receiver;\n\nConsole.WriteLine(\"exit..\");\nConsole.ReadKey();\nasync Task SendMessageThreadAsync(ChannelWriter\u003CMessage> writer,object? arg){\n    int id = (int)arg!;\n    for(int i=1;i\u003C=20;i++){\n        \u002F\u002Fqueue.Add(new Message(id, i.ToString()));\n        await writer.WriteAsync(new Message(id, i.ToString()));\n        Console.WriteLine($\"thread{id} sent {i}\");\n        await Task.Delay(100);\n    }\n}\n\nasync Task ReceiveMessageThreadAsync(ChannelReader\u003CMessage> reader,int id, CancellationToken token){\n    try{\n        while (!token.IsCancellationRequested)\n        {\n            var message = await reader.ReadAsync();\n            Console.WriteLine($\"thread {id} received {message.Content} from {message.FromId}\");\n        }\n    }\n    catch (OperationCanceledException)\n    {\n        Console.WriteLine($\"task {id} cancel!\");\n    }\n}\n\nrecord Message(int FromId, string Content);\n",[152,4335,4336,4340,4344,4350,4356,4362,4366,4370,4374,4379,4384,4389,4394,4399,4404,4409,4413,4417,4421,4426,4430,4434,4438,4443,4447,4452,4456,4460,4464,4469,4473,4478,4482,4487,4491,4495,4499,4504,4508,4513,4517,4521,4525],{"__ignoreMap":150},[155,4337,4338],{"class":157,"line":158},[155,4339,4131],{},[155,4341,4342],{"class":157,"line":164},[155,4343,4136],{},[155,4345,4346,4348],{"class":157,"line":170},[155,4347,4141],{},[155,4349,4144],{},[155,4351,4352,4354],{"class":157,"line":176},[155,4353,4149],{},[155,4355,4152],{},[155,4357,4358,4360],{"class":157,"line":182},[155,4359,4157],{},[155,4361,4160],{},[155,4363,4364],{"class":157,"line":188},[155,4365,4165],{},[155,4367,4368],{"class":157,"line":194},[155,4369,4170],{},[155,4371,4372],{"class":157,"line":200},[155,4373,4179],{},[155,4375,4376],{"class":157,"line":206},[155,4377,4378],{},"using var cts = new CancellationTokenSource();\n",[155,4380,4381],{"class":157,"line":347},[155,4382,4383],{},"var token = cts.Token;\n",[155,4385,4386],{"class":157,"line":353},[155,4387,4388],{},"var sender = SendMessageThreadAsync(channel.Writer, 1);\n",[155,4390,4391],{"class":157,"line":358},[155,4392,4393],{},"var receiver = ReceiveMessageThreadAsync(channel.Reader, 2, token);\n",[155,4395,4396],{"class":157,"line":363},[155,4397,4398],{},"await sender;\n",[155,4400,4401],{"class":157,"line":369},[155,4402,4403],{},"await Task.Delay(100);\n",[155,4405,4406],{"class":157,"line":375},[155,4407,4408],{},"await receiver;\n",[155,4410,4411],{"class":157,"line":381},[155,4412,517],{"emptyLinePlaceholder":516},[155,4414,4415],{"class":157,"line":387},[155,4416,3678],{},[155,4418,4419],{"class":157,"line":393},[155,4420,3683],{},[155,4422,4423],{"class":157,"line":399},[155,4424,4425],{},"async Task SendMessageThreadAsync(ChannelWriter\u003CMessage> writer,object? arg){\n",[155,4427,4428],{"class":157,"line":405},[155,4429,3693],{},[155,4431,4432],{"class":157,"line":411},[155,4433,3698],{},[155,4435,4436],{"class":157,"line":416},[155,4437,4236],{},[155,4439,4440],{"class":157,"line":421},[155,4441,4442],{},"        await writer.WriteAsync(new Message(id, i.ToString()));\n",[155,4444,4445],{"class":157,"line":426},[155,4446,3708],{},[155,4448,4449],{"class":157,"line":432},[155,4450,4451],{},"        await Task.Delay(100);\n",[155,4453,4454],{"class":157,"line":438},[155,4455,656],{},[155,4457,4458],{"class":157,"line":444},[155,4459,661],{},[155,4461,4462],{"class":157,"line":450},[155,4463,517],{"emptyLinePlaceholder":516},[155,4465,4466],{"class":157,"line":455},[155,4467,4468],{},"async Task ReceiveMessageThreadAsync(ChannelReader\u003CMessage> reader,int id, CancellationToken token){\n",[155,4470,4471],{"class":157,"line":461},[155,4472,3503],{},[155,4474,4475],{"class":157,"line":467},[155,4476,4477],{},"        while (!token.IsCancellationRequested)\n",[155,4479,4480],{"class":157,"line":473},[155,4481,1011],{},[155,4483,4484],{"class":157,"line":479},[155,4485,4486],{},"            var message = await reader.ReadAsync();\n",[155,4488,4489],{"class":157,"line":484},[155,4490,3752],{},[155,4492,4493],{"class":157,"line":2058},[155,4494,1031],{},[155,4496,4497],{"class":157,"line":2064},[155,4498,656],{},[155,4500,4501],{"class":157,"line":2070},[155,4502,4503],{},"    catch (OperationCanceledException)\n",[155,4505,4506],{"class":157,"line":2076},[155,4507,607],{},[155,4509,4510],{"class":157,"line":2082},[155,4511,4512],{},"        Console.WriteLine($\"task {id} cancel!\");\n",[155,4514,4515],{"class":157,"line":2088},[155,4516,656],{},[155,4518,4519],{"class":157,"line":2094},[155,4520,661],{},[155,4522,4523],{"class":157,"line":2100},[155,4524,517],{"emptyLinePlaceholder":516},[155,4526,4527],{"class":157,"line":2106},[155,4528,3791],{},[14,4530,4531],{},"那么我们怎么实现在传输完成时候这个channel怎么close呢，我们可以不使用cts。直接使用channel.Writer.Complete(); 并且抛出一个Exception。",[145,4533,4535],{"className":546,"code":4534,"language":548,"meta":150,"style":150},"var channel = Channel.CreateUnbounded\u003CMessage>(\n);\n\nvar sender = SendMessageThreadAsync(channel.Writer, 1);\nvar receiver = ReceiveMessageThreadAsync(channel.Reader, 2);\nawait sender;\nawait Task.Delay(100);\nchannel.Writer.Complete();\nawait receiver;\n\nConsole.WriteLine(\"exit..\");\n\nasync Task SendMessageThreadAsync(ChannelWriter\u003CMessage> writer,object? arg){\n    int id = (int)arg!;\n    for(int i=1;i\u003C=20;i++){\n        \u002F\u002Fqueue.Add(new Message(id, i.ToString()));\n        await writer.WriteAsync(new Message(id, i.ToString()));\n        Console.WriteLine($\"thread{id} sent {i}\");\n        await Task.Delay(100);\n    }\n}\n\nasync Task ReceiveMessageThreadAsync(ChannelReader\u003CMessage> reader,int id){\n    try{\n        while (!reader.Completion.IsCompleted)\n        {\n            var message = await reader.ReadAsync();\n            Console.WriteLine($\"thread {id} received {message.Content} from {message.FromId}\");\n        }\n    }\n    catch (OperationCanceledException)\n    {\n        Console.WriteLine($\"task {id} cancel!\");\n    }\n    catch (ChannelClosedException) {\n        Console.WriteLine(\"closed!\");\n    }\n}\n\nrecord Message(int FromId, string Content);\n",[152,4536,4537,4541,4545,4549,4553,4558,4562,4566,4571,4575,4579,4583,4587,4591,4595,4599,4603,4607,4611,4615,4619,4623,4627,4632,4636,4641,4645,4649,4653,4657,4661,4665,4669,4673,4677,4682,4687,4691,4695,4699],{"__ignoreMap":150},[155,4538,4539],{"class":157,"line":158},[155,4540,4170],{},[155,4542,4543],{"class":157,"line":164},[155,4544,4179],{},[155,4546,4547],{"class":157,"line":170},[155,4548,517],{"emptyLinePlaceholder":516},[155,4550,4551],{"class":157,"line":176},[155,4552,4388],{},[155,4554,4555],{"class":157,"line":182},[155,4556,4557],{},"var receiver = ReceiveMessageThreadAsync(channel.Reader, 2);\n",[155,4559,4560],{"class":157,"line":188},[155,4561,4398],{},[155,4563,4564],{"class":157,"line":194},[155,4565,4403],{},[155,4567,4568],{"class":157,"line":200},[155,4569,4570],{},"channel.Writer.Complete();\n",[155,4572,4573],{"class":157,"line":206},[155,4574,4408],{},[155,4576,4577],{"class":157,"line":347},[155,4578,517],{"emptyLinePlaceholder":516},[155,4580,4581],{"class":157,"line":353},[155,4582,3678],{},[155,4584,4585],{"class":157,"line":358},[155,4586,517],{"emptyLinePlaceholder":516},[155,4588,4589],{"class":157,"line":363},[155,4590,4425],{},[155,4592,4593],{"class":157,"line":369},[155,4594,3693],{},[155,4596,4597],{"class":157,"line":375},[155,4598,3698],{},[155,4600,4601],{"class":157,"line":381},[155,4602,4236],{},[155,4604,4605],{"class":157,"line":387},[155,4606,4442],{},[155,4608,4609],{"class":157,"line":393},[155,4610,3708],{},[155,4612,4613],{"class":157,"line":399},[155,4614,4451],{},[155,4616,4617],{"class":157,"line":405},[155,4618,656],{},[155,4620,4621],{"class":157,"line":411},[155,4622,661],{},[155,4624,4625],{"class":157,"line":416},[155,4626,517],{"emptyLinePlaceholder":516},[155,4628,4629],{"class":157,"line":421},[155,4630,4631],{},"async Task ReceiveMessageThreadAsync(ChannelReader\u003CMessage> reader,int id){\n",[155,4633,4634],{"class":157,"line":426},[155,4635,3503],{},[155,4637,4638],{"class":157,"line":432},[155,4639,4640],{},"        while (!reader.Completion.IsCompleted)\n",[155,4642,4643],{"class":157,"line":438},[155,4644,1011],{},[155,4646,4647],{"class":157,"line":444},[155,4648,4486],{},[155,4650,4651],{"class":157,"line":450},[155,4652,3752],{},[155,4654,4655],{"class":157,"line":455},[155,4656,1031],{},[155,4658,4659],{"class":157,"line":461},[155,4660,656],{},[155,4662,4663],{"class":157,"line":467},[155,4664,4503],{},[155,4666,4667],{"class":157,"line":473},[155,4668,607],{},[155,4670,4671],{"class":157,"line":479},[155,4672,4512],{},[155,4674,4675],{"class":157,"line":484},[155,4676,656],{},[155,4678,4679],{"class":157,"line":2058},[155,4680,4681],{},"    catch (ChannelClosedException) {\n",[155,4683,4684],{"class":157,"line":2064},[155,4685,4686],{},"        Console.WriteLine(\"closed!\");\n",[155,4688,4689],{"class":157,"line":2070},[155,4690,656],{},[155,4692,4693],{"class":157,"line":2076},[155,4694,661],{},[155,4696,4697],{"class":157,"line":2082},[155,4698,517],{"emptyLinePlaceholder":516},[155,4700,4701],{"class":157,"line":2088},[155,4702,3791],{},[289,4704,4705],{"id":4705},"同步方法调用异步的实现",[14,4707,4708],{},"如果我们的场景是在初始化的时候使用一个异步操作去初始化一些信息，就比如说你想要在初始化的时候去使用异步操作对数据库进行IO操作。",[14,4710,4711],{},"![[Pasted image 20260329162825.png]]",[3104,4713,4715],{"id":4714},"fireandforget-发后不理","FireAndForget 发后不理",[14,4717,4718],{},"这种方式可以用在构造函数是异步方法的场景，我们可以创建一个叫做SageFireAndForget的方法来封装try-catch，避免 async void导致的程序直接崩溃的情况。",[145,4720,4722],{"className":546,"code":4721,"language":548,"meta":150,"style":150},"using System;\n\nclass Program\n{\n    class MyDataModel\n    {\n        public List\u003Cint>? Data { get; private set; }\n        public bool IsDataLoaded { get; private set; } = false;\n        public MyDataModel()\n        {\n            SafeFireAndForget(task: LoadDataAsync(),\n            onCompleted:\n            () => IsDataLoaded = true,\n            onError:\n            (e) => throw e);\n        }\n\n        static async void SafeFireAndForget(Task task, Action? onCompleted = null, Action\u003CException>? onError = null)\n        {\n            try\n            {\n                await task;\n            }\n            catch (Exception e)\n            {\n                onError.Invoke(e);\n            }\n        }\n\n        async Task LoadDataAsync()\n        {\n            await Task.Delay(1000);\n            throw new Exception(\"failed to load data\"); \u002F\u002F 测试\n            Data = Enumerable.Range(1, 10).ToList();\n        }\n    }\n    static void Main()\n    {\n        var dataModel = new MyDataModel();\n    }\n}\n",[152,4723,4724,4728,4732,4737,4741,4746,4750,4755,4760,4765,4769,4774,4779,4784,4789,4794,4798,4802,4807,4811,4816,4821,4826,4831,4836,4840,4845,4849,4853,4857,4862,4866,4871,4876,4881,4885,4889,4894,4898,4903,4907],{"__ignoreMap":150},[155,4725,4726],{"class":157,"line":158},[155,4727,1491],{},[155,4729,4730],{"class":157,"line":164},[155,4731,517],{"emptyLinePlaceholder":516},[155,4733,4734],{"class":157,"line":170},[155,4735,4736],{},"class Program\n",[155,4738,4739],{"class":157,"line":176},[155,4740,565],{},[155,4742,4743],{"class":157,"line":182},[155,4744,4745],{},"    class MyDataModel\n",[155,4747,4748],{"class":157,"line":188},[155,4749,607],{},[155,4751,4752],{"class":157,"line":194},[155,4753,4754],{},"        public List\u003Cint>? Data { get; private set; }\n",[155,4756,4757],{"class":157,"line":200},[155,4758,4759],{},"        public bool IsDataLoaded { get; private set; } = false;\n",[155,4761,4762],{"class":157,"line":206},[155,4763,4764],{},"        public MyDataModel()\n",[155,4766,4767],{"class":157,"line":347},[155,4768,1011],{},[155,4770,4771],{"class":157,"line":353},[155,4772,4773],{},"            SafeFireAndForget(task: LoadDataAsync(),\n",[155,4775,4776],{"class":157,"line":358},[155,4777,4778],{},"            onCompleted:\n",[155,4780,4781],{"class":157,"line":363},[155,4782,4783],{},"            () => IsDataLoaded = true,\n",[155,4785,4786],{"class":157,"line":369},[155,4787,4788],{},"            onError:\n",[155,4790,4791],{"class":157,"line":375},[155,4792,4793],{},"            (e) => throw e);\n",[155,4795,4796],{"class":157,"line":381},[155,4797,1031],{},[155,4799,4800],{"class":157,"line":387},[155,4801,517],{"emptyLinePlaceholder":516},[155,4803,4804],{"class":157,"line":393},[155,4805,4806],{},"        static async void SafeFireAndForget(Task task, Action? onCompleted = null, Action\u003CException>? onError = null)\n",[155,4808,4809],{"class":157,"line":399},[155,4810,1011],{},[155,4812,4813],{"class":157,"line":405},[155,4814,4815],{},"            try\n",[155,4817,4818],{"class":157,"line":411},[155,4819,4820],{},"            {\n",[155,4822,4823],{"class":157,"line":416},[155,4824,4825],{},"                await task;\n",[155,4827,4828],{"class":157,"line":421},[155,4829,4830],{},"            }\n",[155,4832,4833],{"class":157,"line":426},[155,4834,4835],{},"            catch (Exception e)\n",[155,4837,4838],{"class":157,"line":432},[155,4839,4820],{},[155,4841,4842],{"class":157,"line":438},[155,4843,4844],{},"                onError.Invoke(e);\n",[155,4846,4847],{"class":157,"line":444},[155,4848,4830],{},[155,4850,4851],{"class":157,"line":450},[155,4852,1031],{},[155,4854,4855],{"class":157,"line":455},[155,4856,517],{"emptyLinePlaceholder":516},[155,4858,4859],{"class":157,"line":461},[155,4860,4861],{},"        async Task LoadDataAsync()\n",[155,4863,4864],{"class":157,"line":467},[155,4865,1011],{},[155,4867,4868],{"class":157,"line":473},[155,4869,4870],{},"            await Task.Delay(1000);\n",[155,4872,4873],{"class":157,"line":479},[155,4874,4875],{},"            throw new Exception(\"failed to load data\"); \u002F\u002F 测试\n",[155,4877,4878],{"class":157,"line":484},[155,4879,4880],{},"            Data = Enumerable.Range(1, 10).ToList();\n",[155,4882,4883],{"class":157,"line":2058},[155,4884,1031],{},[155,4886,4887],{"class":157,"line":2064},[155,4888,656],{},[155,4890,4891],{"class":157,"line":2070},[155,4892,4893],{},"    static void Main()\n",[155,4895,4896],{"class":157,"line":2076},[155,4897,607],{},[155,4899,4900],{"class":157,"line":2082},[155,4901,4902],{},"        var dataModel = new MyDataModel();\n",[155,4904,4905],{"class":157,"line":2088},[155,4906,656],{},[155,4908,4909],{"class":157,"line":2094},[155,4910,661],{},[14,4912,4913],{},"还有一种是使用异步工厂函数，不在构造函数里运行逻辑，而是把构造函数设置为private，通过一个静态异步方法来创建实例。",[145,4915,4917],{"className":546,"code":4916,"language":548,"meta":150,"style":150},"public class MyDataModel\n{\n    public List\u003Cint>? Data { get; private set; }\n    \n    \u002F\u002F 私有化构造函数\n    private MyDataModel() { }\n\n    \u002F\u002F 静态异步工厂\n    public static async Task\u003CMyDataModel> CreateAsync()\n    {\n        var model = new MyDataModel();\n        await model.LoadDataAsync(); \u002F\u002F 这里可以安全地 await\n        return model;\n    }\n\n    private async Task LoadDataAsync()\n    {\n        await Task.Delay(1000);\n        Data = Enumerable.Range(1, 10).ToList();\n    }\n}\n\n\u002F\u002F 使用时：\nvar dataModel = await MyDataModel.CreateAsync();\n",[152,4918,4919,4924,4928,4933,4937,4942,4947,4951,4956,4961,4965,4970,4975,4980,4984,4988,4993,4997,5002,5007,5011,5015,5019,5024],{"__ignoreMap":150},[155,4920,4921],{"class":157,"line":158},[155,4922,4923],{},"public class MyDataModel\n",[155,4925,4926],{"class":157,"line":164},[155,4927,565],{},[155,4929,4930],{"class":157,"line":170},[155,4931,4932],{},"    public List\u003Cint>? Data { get; private set; }\n",[155,4934,4935],{"class":157,"line":176},[155,4936,339],{},[155,4938,4939],{"class":157,"line":182},[155,4940,4941],{},"    \u002F\u002F 私有化构造函数\n",[155,4943,4944],{"class":157,"line":188},[155,4945,4946],{},"    private MyDataModel() { }\n",[155,4948,4949],{"class":157,"line":194},[155,4950,517],{"emptyLinePlaceholder":516},[155,4952,4953],{"class":157,"line":200},[155,4954,4955],{},"    \u002F\u002F 静态异步工厂\n",[155,4957,4958],{"class":157,"line":206},[155,4959,4960],{},"    public static async Task\u003CMyDataModel> CreateAsync()\n",[155,4962,4963],{"class":157,"line":347},[155,4964,607],{},[155,4966,4967],{"class":157,"line":353},[155,4968,4969],{},"        var model = new MyDataModel();\n",[155,4971,4972],{"class":157,"line":358},[155,4973,4974],{},"        await model.LoadDataAsync(); \u002F\u002F 这里可以安全地 await\n",[155,4976,4977],{"class":157,"line":363},[155,4978,4979],{},"        return model;\n",[155,4981,4982],{"class":157,"line":369},[155,4983,656],{},[155,4985,4986],{"class":157,"line":375},[155,4987,517],{"emptyLinePlaceholder":516},[155,4989,4990],{"class":157,"line":381},[155,4991,4992],{},"    private async Task LoadDataAsync()\n",[155,4994,4995],{"class":157,"line":387},[155,4996,607],{},[155,4998,4999],{"class":157,"line":393},[155,5000,5001],{},"        await Task.Delay(1000);\n",[155,5003,5004],{"class":157,"line":399},[155,5005,5006],{},"        Data = Enumerable.Range(1, 10).ToList();\n",[155,5008,5009],{"class":157,"line":405},[155,5010,656],{},[155,5012,5013],{"class":157,"line":411},[155,5014,661],{},[155,5016,5017],{"class":157,"line":416},[155,5018,517],{"emptyLinePlaceholder":516},[155,5020,5021],{"class":157,"line":421},[155,5022,5023],{},"\u002F\u002F 使用时：\n",[155,5025,5026],{"class":157,"line":426},[155,5027,5028],{},"var dataModel = await MyDataModel.CreateAsync();\n",[3104,5030,5031],{"id":5031},"异步任务中实现同步机制",[14,5033,5034,5035,5038,5039,29],{},"在C#中要实现异步任务的同步机制，我们需要使用信号量同步，我们需要使用SemaphoreSlim，这个是异步环境中最推荐的同步锁，比lock关键字更加强大，因为它支持 ",[152,5036,5037],{},"WaitAsync()","，但你也可以在异步任务里使用它的同步 ",[152,5040,5041],{},"Wait()",[14,5043,5044,5045,5048,5049,5052],{},"它的使用场景比较宽广，就比如限制并发请求数（流量控制、限流）。保护非线程安全的资源，比如你有一个",[18,5046,5047],{},"单例的文件写入流","或",[18,5050,5051],{},"串口通信对象","，异步任务 A 和 B 不能同时往里面写东西。",[145,5054,5056],{"className":546,"code":5055,"language":548,"meta":150,"style":150},"private static readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);\n\npublic void DoSomething()\n{\n    _locker.Wait(); \u002F\u002F 同步阻塞，确保只有一个任务进入\n    try {\n        \u002F\u002F 执行关键逻辑\n    }\n    finally {\n        _locker.Release();\n    }\n}\n\n\u002F\u002F 异步任务里也可以使用\nasync Task\u003Cint> ComputeAsync(int x){\nawait _locker.WaitAsync();\nawait Task.Delay(300);\n_locker.Release();  \u002F\u002F 记得释放\nreturn x + x;_\n}\n",[152,5057,5058,5063,5067,5072,5076,5081,5086,5091,5095,5100,5105,5109,5113,5117,5122,5127,5132,5137,5142,5147],{"__ignoreMap":150},[155,5059,5060],{"class":157,"line":158},[155,5061,5062],{},"private static readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);\n",[155,5064,5065],{"class":157,"line":164},[155,5066,517],{"emptyLinePlaceholder":516},[155,5068,5069],{"class":157,"line":170},[155,5070,5071],{},"public void DoSomething()\n",[155,5073,5074],{"class":157,"line":176},[155,5075,565],{},[155,5077,5078],{"class":157,"line":182},[155,5079,5080],{},"    _locker.Wait(); \u002F\u002F 同步阻塞，确保只有一个任务进入\n",[155,5082,5083],{"class":157,"line":188},[155,5084,5085],{},"    try {\n",[155,5087,5088],{"class":157,"line":194},[155,5089,5090],{},"        \u002F\u002F 执行关键逻辑\n",[155,5092,5093],{"class":157,"line":200},[155,5094,656],{},[155,5096,5097],{"class":157,"line":206},[155,5098,5099],{},"    finally {\n",[155,5101,5102],{"class":157,"line":347},[155,5103,5104],{},"        _locker.Release();\n",[155,5106,5107],{"class":157,"line":353},[155,5108,656],{},[155,5110,5111],{"class":157,"line":358},[155,5112,661],{},[155,5114,5115],{"class":157,"line":363},[155,5116,517],{"emptyLinePlaceholder":516},[155,5118,5119],{"class":157,"line":369},[155,5120,5121],{},"\u002F\u002F 异步任务里也可以使用\n",[155,5123,5124],{"class":157,"line":375},[155,5125,5126],{},"async Task\u003Cint> ComputeAsync(int x){\n",[155,5128,5129],{"class":157,"line":381},[155,5130,5131],{},"await _locker.WaitAsync();\n",[155,5133,5134],{"class":157,"line":387},[155,5135,5136],{},"await Task.Delay(300);\n",[155,5138,5139],{"class":157,"line":393},[155,5140,5141],{},"_locker.Release();  \u002F\u002F 记得释放\n",[155,5143,5144],{"class":157,"line":399},[155,5145,5146],{},"return x + x;_\n",[155,5148,5149],{"class":157,"line":405},[155,5150,661],{},[289,5152,5154],{"id":5153},"valuetask","ValueTask",[14,5156,5157,5159,5160],{},[152,5158,5154],{}," 是优化内存和压榨性能的神器，简单来说：",[18,5161,5162,5164,5165,5167],{},[152,5163,2668],{}," 是一个类（Class），而 ",[152,5166,5154],{}," 是一个结构体（Struct）。",[14,5169,5170,5171,5174,5175,5177,5178,29],{},"我们每创建一个Task的时候，系统都会在",[18,5172,5173],{},"堆上分配内存。 如果你的异步方法","经常直接返回结果（例如数据已经缓存好了，不需要真的去跑异步逻辑），频繁创建 ",[152,5176,2668],{}," 对象会产生大量的 ",[18,5179,5180],{},"GC（垃圾回收）压力",[14,5182,5183,5184,5187],{},"而ValueTask是作为值类型，存储在",[18,5185,5186],{},"栈上，如果结果立即可用，它","零分配内存，因此使用它可以有效地减少频繁调用的GC开销。",[14,5189,5190,5191,5194,5195,5197,5198,1142],{},"我们用一个高性能的",[152,5192,5193],{},"MyDataService"," 示例，展示了如何结合 ",[152,5196,5154],{}," 和之前的 ",[152,5199,5200],{},"SemaphoreSlim",[145,5202,5204],{"className":546,"code":5203,"language":548,"meta":150,"style":150},"using System;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\npublic class MyDataService\n{\n    private List\u003Cint>? _cachedData;\n    private DateTime _lastFetchTime;\n    private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);\n    private readonly TimeSpan _cacheDuration = TimeSpan.FromSeconds(5);\n\n    \u002F\u002F 使用 ValueTask\u003CT> 减少内存分配\n    public async ValueTask\u003CList\u003Cint>> GetDataAsync()\n    {\n        \u002F\u002F 场景 A: 缓存有效，直接从栈（Stack）返回结果，零堆分配 (Zero Allocation)\n        if (_cachedData != null && (DateTime.Now - _lastFetchTime) \u003C _cacheDuration)\n        {\n            return _cachedData; \n        }\n\n        \u002F\u002F 场景 B: 需要真正执行异步逻辑\n        await _lock.WaitAsync();\n        try\n        {\n            \u002F\u002F 双重检查（Double-check locking）\n            if (_cachedData != null && (DateTime.Now - _lastFetchTime) \u003C _cacheDuration)\n            {\n                return _cachedData;\n            }\n\n            \u002F\u002F 模拟从 PLC 或 数据库读取数据\n            _cachedData = await FetchFromHardwareAsync();\n            _lastFetchTime = DateTime.Now;\n            \n            return _cachedData;\n        }\n        finally\n        {\n            _lock.Release();\n        }\n    }\n\n    private async Task\u003CList\u003Cint>> FetchFromHardwareAsync()\n    {\n        await Task.Delay(100); \u002F\u002F 模拟硬件延迟\n        return new List\u003Cint> { 1, 2, 3, 4, 5 };\n    }\n}\n",[152,5205,5206,5210,5215,5219,5224,5228,5233,5237,5242,5247,5252,5257,5261,5266,5271,5275,5280,5285,5289,5294,5298,5302,5307,5312,5317,5321,5326,5331,5335,5340,5344,5348,5353,5358,5363,5367,5372,5376,5381,5385,5390,5394,5398,5402,5407,5411,5416,5421,5425],{"__ignoreMap":150},[155,5207,5208],{"class":157,"line":158},[155,5209,1491],{},[155,5211,5212],{"class":157,"line":164},[155,5213,5214],{},"using System.Collections.Generic;\n",[155,5216,5217],{"class":157,"line":170},[155,5218,1496],{},[155,5220,5221],{"class":157,"line":176},[155,5222,5223],{},"using System.Threading.Tasks;\n",[155,5225,5226],{"class":157,"line":182},[155,5227,517],{"emptyLinePlaceholder":516},[155,5229,5230],{"class":157,"line":188},[155,5231,5232],{},"public class MyDataService\n",[155,5234,5235],{"class":157,"line":194},[155,5236,565],{},[155,5238,5239],{"class":157,"line":200},[155,5240,5241],{},"    private List\u003Cint>? _cachedData;\n",[155,5243,5244],{"class":157,"line":206},[155,5245,5246],{},"    private DateTime _lastFetchTime;\n",[155,5248,5249],{"class":157,"line":347},[155,5250,5251],{},"    private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);\n",[155,5253,5254],{"class":157,"line":353},[155,5255,5256],{},"    private readonly TimeSpan _cacheDuration = TimeSpan.FromSeconds(5);\n",[155,5258,5259],{"class":157,"line":358},[155,5260,517],{"emptyLinePlaceholder":516},[155,5262,5263],{"class":157,"line":363},[155,5264,5265],{},"    \u002F\u002F 使用 ValueTask\u003CT> 减少内存分配\n",[155,5267,5268],{"class":157,"line":369},[155,5269,5270],{},"    public async ValueTask\u003CList\u003Cint>> GetDataAsync()\n",[155,5272,5273],{"class":157,"line":375},[155,5274,607],{},[155,5276,5277],{"class":157,"line":381},[155,5278,5279],{},"        \u002F\u002F 场景 A: 缓存有效，直接从栈（Stack）返回结果，零堆分配 (Zero Allocation)\n",[155,5281,5282],{"class":157,"line":387},[155,5283,5284],{},"        if (_cachedData != null && (DateTime.Now - _lastFetchTime) \u003C _cacheDuration)\n",[155,5286,5287],{"class":157,"line":393},[155,5288,1011],{},[155,5290,5291],{"class":157,"line":399},[155,5292,5293],{},"            return _cachedData; \n",[155,5295,5296],{"class":157,"line":405},[155,5297,1031],{},[155,5299,5300],{"class":157,"line":411},[155,5301,517],{"emptyLinePlaceholder":516},[155,5303,5304],{"class":157,"line":416},[155,5305,5306],{},"        \u002F\u002F 场景 B: 需要真正执行异步逻辑\n",[155,5308,5309],{"class":157,"line":421},[155,5310,5311],{},"        await _lock.WaitAsync();\n",[155,5313,5314],{"class":157,"line":426},[155,5315,5316],{},"        try\n",[155,5318,5319],{"class":157,"line":432},[155,5320,1011],{},[155,5322,5323],{"class":157,"line":438},[155,5324,5325],{},"            \u002F\u002F 双重检查（Double-check locking）\n",[155,5327,5328],{"class":157,"line":444},[155,5329,5330],{},"            if (_cachedData != null && (DateTime.Now - _lastFetchTime) \u003C _cacheDuration)\n",[155,5332,5333],{"class":157,"line":450},[155,5334,4820],{},[155,5336,5337],{"class":157,"line":455},[155,5338,5339],{},"                return _cachedData;\n",[155,5341,5342],{"class":157,"line":461},[155,5343,4830],{},[155,5345,5346],{"class":157,"line":467},[155,5347,517],{"emptyLinePlaceholder":516},[155,5349,5350],{"class":157,"line":473},[155,5351,5352],{},"            \u002F\u002F 模拟从 PLC 或 数据库读取数据\n",[155,5354,5355],{"class":157,"line":479},[155,5356,5357],{},"            _cachedData = await FetchFromHardwareAsync();\n",[155,5359,5360],{"class":157,"line":484},[155,5361,5362],{},"            _lastFetchTime = DateTime.Now;\n",[155,5364,5365],{"class":157,"line":2058},[155,5366,1531],{},[155,5368,5369],{"class":157,"line":2064},[155,5370,5371],{},"            return _cachedData;\n",[155,5373,5374],{"class":157,"line":2070},[155,5375,1031],{},[155,5377,5378],{"class":157,"line":2076},[155,5379,5380],{},"        finally\n",[155,5382,5383],{"class":157,"line":2082},[155,5384,1011],{},[155,5386,5387],{"class":157,"line":2088},[155,5388,5389],{},"            _lock.Release();\n",[155,5391,5392],{"class":157,"line":2094},[155,5393,1031],{},[155,5395,5396],{"class":157,"line":2100},[155,5397,656],{},[155,5399,5400],{"class":157,"line":2106},[155,5401,517],{"emptyLinePlaceholder":516},[155,5403,5404],{"class":157,"line":2111},[155,5405,5406],{},"    private async Task\u003CList\u003Cint>> FetchFromHardwareAsync()\n",[155,5408,5409],{"class":157,"line":2116},[155,5410,607],{},[155,5412,5413],{"class":157,"line":2121},[155,5414,5415],{},"        await Task.Delay(100); \u002F\u002F 模拟硬件延迟\n",[155,5417,5418],{"class":157,"line":2127},[155,5419,5420],{},"        return new List\u003Cint> { 1, 2, 3, 4, 5 };\n",[155,5422,5423],{"class":157,"line":2132},[155,5424,656],{},[155,5426,5427],{"class":157,"line":2138},[155,5428,661],{},[289,5430,5431],{"id":5431},"追踪异步任务进度",[145,5433,5435],{"className":546,"code":5434,"language":548,"meta":150,"style":150},"public partial class MainWindow : Window\n{\n    public MainWindow()\n    {\n        InitializeComponent();\n    }\n\n    private async void Button_Click(object sender, RoutedEventArgs e)\n    {\n        button.IsEnabled = false;\n        \u002F\u002F job\n        try\n        {\n            await DoJobAsync();\n        }\n        catch (Exception)\n        {\n\n        }\n        button.IsEnabled = true;\n    }\n\n    async Task DoJobAsync(\n        )\n    {\n        for (int i = 1; i \u003C= 100; i++)\n        {\n            await Task.Delay(50);\n            progressBar.Value = i;\n        }\n    }\n}\n",[152,5436,5437,5442,5446,5451,5455,5460,5464,5468,5473,5477,5482,5487,5491,5495,5500,5504,5509,5513,5517,5521,5526,5530,5534,5539,5544,5548,5553,5557,5562,5567,5571,5575],{"__ignoreMap":150},[155,5438,5439],{"class":157,"line":158},[155,5440,5441],{},"public partial class MainWindow : Window\n",[155,5443,5444],{"class":157,"line":164},[155,5445,565],{},[155,5447,5448],{"class":157,"line":170},[155,5449,5450],{},"    public MainWindow()\n",[155,5452,5453],{"class":157,"line":176},[155,5454,607],{},[155,5456,5457],{"class":157,"line":182},[155,5458,5459],{},"        InitializeComponent();\n",[155,5461,5462],{"class":157,"line":188},[155,5463,656],{},[155,5465,5466],{"class":157,"line":194},[155,5467,517],{"emptyLinePlaceholder":516},[155,5469,5470],{"class":157,"line":200},[155,5471,5472],{},"    private async void Button_Click(object sender, RoutedEventArgs e)\n",[155,5474,5475],{"class":157,"line":206},[155,5476,607],{},[155,5478,5479],{"class":157,"line":347},[155,5480,5481],{},"        button.IsEnabled = false;\n",[155,5483,5484],{"class":157,"line":353},[155,5485,5486],{},"        \u002F\u002F job\n",[155,5488,5489],{"class":157,"line":358},[155,5490,5316],{},[155,5492,5493],{"class":157,"line":363},[155,5494,1011],{},[155,5496,5497],{"class":157,"line":369},[155,5498,5499],{},"            await DoJobAsync();\n",[155,5501,5502],{"class":157,"line":375},[155,5503,1031],{},[155,5505,5506],{"class":157,"line":381},[155,5507,5508],{},"        catch (Exception)\n",[155,5510,5511],{"class":157,"line":387},[155,5512,1011],{},[155,5514,5515],{"class":157,"line":393},[155,5516,517],{"emptyLinePlaceholder":516},[155,5518,5519],{"class":157,"line":399},[155,5520,1031],{},[155,5522,5523],{"class":157,"line":405},[155,5524,5525],{},"        button.IsEnabled = true;\n",[155,5527,5528],{"class":157,"line":411},[155,5529,656],{},[155,5531,5532],{"class":157,"line":416},[155,5533,517],{"emptyLinePlaceholder":516},[155,5535,5536],{"class":157,"line":421},[155,5537,5538],{},"    async Task DoJobAsync(\n",[155,5540,5541],{"class":157,"line":426},[155,5542,5543],{},"        )\n",[155,5545,5546],{"class":157,"line":432},[155,5547,607],{},[155,5549,5550],{"class":157,"line":438},[155,5551,5552],{},"        for (int i = 1; i \u003C= 100; i++)\n",[155,5554,5555],{"class":157,"line":444},[155,5556,1011],{},[155,5558,5559],{"class":157,"line":450},[155,5560,5561],{},"            await Task.Delay(50);\n",[155,5563,5564],{"class":157,"line":455},[155,5565,5566],{},"            progressBar.Value = i;\n",[155,5568,5569],{"class":157,"line":461},[155,5570,1031],{},[155,5572,5573],{"class":157,"line":467},[155,5574,656],{},[155,5576,5577],{"class":157,"line":473},[155,5578,661],{},[14,5580,5581],{},"或者可以用IProgerss:\n![[Pasted image 20260330003009.png]]",[289,5583,5585],{"id":5584},"asyncbarrier实现多个异步同时完成","AsyncBarrier实现多个异步同时完成",[14,5587,5588,5589,5592,5593,5596,5597,5601,5602,5607],{},"最地道的做法是使用Barrier的异步扩展或者是Task.WhenAll。由于 C# 标准库中没有直接叫 ",[152,5590,5591],{},"AsyncBarrier"," 的类（通常需要自己封装或使用三方库如 ",[152,5594,5595],{},"Microsoft.VisualStudio.Threading","），我们可以用 ",[18,5598,5599],{},[152,5600,5200],{}," 或 ",[18,5603,5604],{},[152,5605,5606],{},"TaskCompletionSource"," 来实现这个经典的“栅栏”模式。",[14,5609,5610],{},"这种开发模式和使用Task.WhelAll是有区别的，因为Task.WhenAll是适用于一次性的任务，比如我们直接启动一堆任务，等待它们完成。但是AsyncBarrier是适用于多阶段的循环任务，就比如我们的PLC扫描循环。",[5612,5613,5614,5617,5620],"ul",{},[2973,5615,5616],{},"阶段一：所有人读完寄存器（等待）。",[2973,5618,5619],{},"阶段二：所有人处理完数据（等待）。",[2973,5621,5622],{},"循环回第一阶段。",[14,5624,5625],{},"他和SemaphoreSlim 有种天然相反的感觉，后者是相当于一个固定窗口，只允许限额的任务进去，而前者则是必须达到了限定的额数才开始执行。",[14,5627,5628],{},"如果我们不去自己实现的话，也可以去安装Nito.AsyncEX。",[145,5630,5632],{"className":546,"code":5631,"language":548,"meta":150,"style":150},"using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace AsyncBarrierSolution;\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F An asynchronous barrier that blocks the signaler until all other participants have signaled.\n\u002F\u002F\u002F \u003C\u002Fsummary>\npublic class AsyncBarrier\n{\n    \u002F\u002F\u002F \u003Csummary>\n    \u002F\u002F\u002F The number of participants being synchronized.\n    \u002F\u002F\u002F \u003C\u002Fsummary>\n    private readonly int participantCount;\n\n    \u002F\u002F\u002F \u003Csummary>\n    \u002F\u002F\u002F The set of participants who have reached the barrier, with their awaiters that can resume those participants.\n    \u002F\u002F\u002F \u003C\u002Fsummary>\n    private readonly Stack\u003CWaiter> waiters;\n\n    \u002F\u002F\u002F \u003Csummary>\n    \u002F\u002F\u002F Initializes a new instance of the \u003Csee cref=\"AsyncBarrier\"\u002F> class.\n    \u002F\u002F\u002F \u003C\u002Fsummary>\n    \u002F\u002F\u002F \u003Cparam name=\"participants\">The number of participants.\u003C\u002Fparam>\n    public AsyncBarrier(int participants)\n    {\n        if (participantCount \u003C 0) \n            throw new ArgumentOutOfRangeException(\n                nameof(participantCount),\n            $\"Argument{nameof(participantCount)} must be a positiive number\"\n            );\n        this.participantCount = participants;\n\n        \u002F\u002F Allocate the stack so no resizing is necessary.\n        \u002F\u002F We don't need space for the last participant, since we never have to store it.\n        this.waiters = new Stack\u003CWaiter>(participants - 1);\n    }\n\n    \u002F\u002F\u002F \u003Cinheritdoc cref=\"SignalAndWait(CancellationToken)\" \u002F>\n    public Task SignalAndWait() => this.SignalAndWait(CancellationToken.None).AsTask();\n\n    \u002F\u002F\u002F \u003Csummary>\n    \u002F\u002F\u002F Signals that a participant is ready, and returns a Task\n    \u002F\u002F\u002F that completes when all other participants have also signaled ready.\n    \u002F\u002F\u002F \u003C\u002Fsummary>\n    \u002F\u002F\u002F \u003Cparam name=\"cancellationToken\">\n    \u002F\u002F\u002F A token that signals the caller's lost interest in waiting.\n    \u002F\u002F\u002F The signal effect of the method is not canceled with the token.\n    \u002F\u002F\u002F \u003C\u002Fparam>\n    \u002F\u002F\u002F \u003Creturns>A task which will complete (or may already be completed) when the last participant calls this method.\u003C\u002Freturns>\n    public ValueTask SignalAndWait(CancellationToken cancellationToken)\n    {\n        lock (this.waiters)\n        {\n            if (this.waiters.Count + 1 == this.participantCount)\n            {\n                \u002F\u002F This is the last one we were waiting for.\n                \u002F\u002F Unleash everyone that preceded this one.\n                while (this.waiters.Count > 0)\n                {\n                    Waiter waiter = this.waiters.Pop();\n                    waiter.CompletionSource.TrySetResult(default);\n                    waiter.CancellationRegistration.Dispose();\n                }\n\n                \u002F\u002F And allow this one to continue immediately.\n                return new ValueTask(cancellationToken.IsCancellationRequested\n                    ? Task.FromCanceled(cancellationToken)\n                    : Task.CompletedTask);\n            }\n            else\n            {\n                \u002F\u002F We need more folks. So suspend this caller.\n                TaskCompletionSource\u003CEmptyStruct> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);\n                CancellationTokenRegistration ctr;\n                if (cancellationToken.CanBeCanceled)\n                {\n#if NET\n                    ctr = cancellationToken.Register(\n                        static (tcs, ct) => ((TaskCompletionSource\u003CEmptyStruct>)tcs!).TrySetCanceled(ct), tcs);\n#else\n                    ctr = cancellationToken.Register(\n                        static s =>\n                        {\n                            var t = (Tuple\u003CTaskCompletionSource\u003CEmptyStruct>, CancellationToken>)s!;\n                            t.Item1.TrySetCanceled(t.Item2);\n                        },\n                        Tuple.Create(tcs, cancellationToken));\n#endif\n                }\n                else\n                {\n                    ctr = default;\n                }\n\n                this.waiters.Push(new Waiter(tcs, ctr));\n                return new ValueTask(tcs.Task);\n            }\n        }\n    }\n\n    private readonly struct Waiter(TaskCompletionSource\u003CEmptyStruct> completionSource, CancellationTokenRegistration cancellationRegistration)\n    {\n        internal readonly TaskCompletionSource\u003CEmptyStruct> CompletionSource => completionSource;\n\n        internal readonly CancellationTokenRegistration CancellationRegistration => cancellationRegistration;\n    }\n}\n\n\u002F\u002F\u002F \u003Csummary>\n\u002F\u002F\u002F An empty struct.\n\u002F\u002F\u002F \u003C\u002Fsummary>\n\u002F\u002F\u002F \u003Cremarks>\n\u002F\u002F\u002F This can save 4 bytes over System.Object when a type argument is required for a generic type, but entirely unused.\n\u002F\u002F\u002F \u003C\u002Fremarks>\ninternal readonly struct EmptyStruct\n{\n    \u002F\u002F\u002F \u003Csummary>\n    \u002F\u002F\u002F Gets an instance of the empty struct.\n    \u002F\u002F\u002F \u003C\u002Fsummary>\n    internal static EmptyStruct Instance\n    {\n        get { return default(EmptyStruct); }\n    }\n}\n",[152,5633,5634,5638,5642,5647,5651,5656,5660,5665,5670,5675,5680,5684,5689,5694,5699,5704,5708,5712,5717,5721,5726,5730,5734,5739,5743,5748,5753,5757,5762,5767,5772,5777,5782,5787,5791,5796,5801,5806,5810,5814,5819,5824,5828,5832,5837,5842,5846,5851,5856,5861,5866,5871,5876,5880,5885,5889,5894,5898,5903,5908,5913,5918,5923,5928,5933,5938,5942,5947,5952,5957,5962,5966,5971,5975,5980,5985,5990,5995,5999,6004,6009,6014,6019,6023,6028,6033,6038,6043,6048,6053,6058,6062,6067,6071,6076,6080,6084,6089,6094,6098,6102,6106,6110,6115,6119,6124,6128,6133,6137,6141,6145,6149,6154,6158,6163,6168,6173,6178,6182,6186,6191,6195,6200,6204,6209,6213],{"__ignoreMap":150},[155,5635,5636],{"class":157,"line":158},[155,5637,1491],{},[155,5639,5640],{"class":157,"line":164},[155,5641,5214],{},[155,5643,5644],{"class":157,"line":170},[155,5645,5646],{},"using System.Text;\n",[155,5648,5649],{"class":157,"line":176},[155,5650,517],{"emptyLinePlaceholder":516},[155,5652,5653],{"class":157,"line":182},[155,5654,5655],{},"namespace AsyncBarrierSolution;\n",[155,5657,5658],{"class":157,"line":188},[155,5659,517],{"emptyLinePlaceholder":516},[155,5661,5662],{"class":157,"line":194},[155,5663,5664],{},"\u002F\u002F\u002F \u003Csummary>\n",[155,5666,5667],{"class":157,"line":200},[155,5668,5669],{},"\u002F\u002F\u002F An asynchronous barrier that blocks the signaler until all other participants have signaled.\n",[155,5671,5672],{"class":157,"line":206},[155,5673,5674],{},"\u002F\u002F\u002F \u003C\u002Fsummary>\n",[155,5676,5677],{"class":157,"line":347},[155,5678,5679],{},"public class AsyncBarrier\n",[155,5681,5682],{"class":157,"line":353},[155,5683,565],{},[155,5685,5686],{"class":157,"line":358},[155,5687,5688],{},"    \u002F\u002F\u002F \u003Csummary>\n",[155,5690,5691],{"class":157,"line":363},[155,5692,5693],{},"    \u002F\u002F\u002F The number of participants being synchronized.\n",[155,5695,5696],{"class":157,"line":369},[155,5697,5698],{},"    \u002F\u002F\u002F \u003C\u002Fsummary>\n",[155,5700,5701],{"class":157,"line":375},[155,5702,5703],{},"    private readonly int participantCount;\n",[155,5705,5706],{"class":157,"line":381},[155,5707,517],{"emptyLinePlaceholder":516},[155,5709,5710],{"class":157,"line":387},[155,5711,5688],{},[155,5713,5714],{"class":157,"line":393},[155,5715,5716],{},"    \u002F\u002F\u002F The set of participants who have reached the barrier, with their awaiters that can resume those participants.\n",[155,5718,5719],{"class":157,"line":399},[155,5720,5698],{},[155,5722,5723],{"class":157,"line":405},[155,5724,5725],{},"    private readonly Stack\u003CWaiter> waiters;\n",[155,5727,5728],{"class":157,"line":411},[155,5729,517],{"emptyLinePlaceholder":516},[155,5731,5732],{"class":157,"line":416},[155,5733,5688],{},[155,5735,5736],{"class":157,"line":421},[155,5737,5738],{},"    \u002F\u002F\u002F Initializes a new instance of the \u003Csee cref=\"AsyncBarrier\"\u002F> class.\n",[155,5740,5741],{"class":157,"line":426},[155,5742,5698],{},[155,5744,5745],{"class":157,"line":432},[155,5746,5747],{},"    \u002F\u002F\u002F \u003Cparam name=\"participants\">The number of participants.\u003C\u002Fparam>\n",[155,5749,5750],{"class":157,"line":438},[155,5751,5752],{},"    public AsyncBarrier(int participants)\n",[155,5754,5755],{"class":157,"line":444},[155,5756,607],{},[155,5758,5759],{"class":157,"line":450},[155,5760,5761],{},"        if (participantCount \u003C 0) \n",[155,5763,5764],{"class":157,"line":455},[155,5765,5766],{},"            throw new ArgumentOutOfRangeException(\n",[155,5768,5769],{"class":157,"line":461},[155,5770,5771],{},"                nameof(participantCount),\n",[155,5773,5774],{"class":157,"line":467},[155,5775,5776],{},"            $\"Argument{nameof(participantCount)} must be a positiive number\"\n",[155,5778,5779],{"class":157,"line":473},[155,5780,5781],{},"            );\n",[155,5783,5784],{"class":157,"line":479},[155,5785,5786],{},"        this.participantCount = participants;\n",[155,5788,5789],{"class":157,"line":484},[155,5790,517],{"emptyLinePlaceholder":516},[155,5792,5793],{"class":157,"line":2058},[155,5794,5795],{},"        \u002F\u002F Allocate the stack so no resizing is necessary.\n",[155,5797,5798],{"class":157,"line":2064},[155,5799,5800],{},"        \u002F\u002F We don't need space for the last participant, since we never have to store it.\n",[155,5802,5803],{"class":157,"line":2070},[155,5804,5805],{},"        this.waiters = new Stack\u003CWaiter>(participants - 1);\n",[155,5807,5808],{"class":157,"line":2076},[155,5809,656],{},[155,5811,5812],{"class":157,"line":2082},[155,5813,517],{"emptyLinePlaceholder":516},[155,5815,5816],{"class":157,"line":2088},[155,5817,5818],{},"    \u002F\u002F\u002F \u003Cinheritdoc cref=\"SignalAndWait(CancellationToken)\" \u002F>\n",[155,5820,5821],{"class":157,"line":2094},[155,5822,5823],{},"    public Task SignalAndWait() => this.SignalAndWait(CancellationToken.None).AsTask();\n",[155,5825,5826],{"class":157,"line":2100},[155,5827,517],{"emptyLinePlaceholder":516},[155,5829,5830],{"class":157,"line":2106},[155,5831,5688],{},[155,5833,5834],{"class":157,"line":2111},[155,5835,5836],{},"    \u002F\u002F\u002F Signals that a participant is ready, and returns a Task\n",[155,5838,5839],{"class":157,"line":2116},[155,5840,5841],{},"    \u002F\u002F\u002F that completes when all other participants have also signaled ready.\n",[155,5843,5844],{"class":157,"line":2121},[155,5845,5698],{},[155,5847,5848],{"class":157,"line":2127},[155,5849,5850],{},"    \u002F\u002F\u002F \u003Cparam name=\"cancellationToken\">\n",[155,5852,5853],{"class":157,"line":2132},[155,5854,5855],{},"    \u002F\u002F\u002F A token that signals the caller's lost interest in waiting.\n",[155,5857,5858],{"class":157,"line":2138},[155,5859,5860],{},"    \u002F\u002F\u002F The signal effect of the method is not canceled with the token.\n",[155,5862,5863],{"class":157,"line":2144},[155,5864,5865],{},"    \u002F\u002F\u002F \u003C\u002Fparam>\n",[155,5867,5868],{"class":157,"line":2149},[155,5869,5870],{},"    \u002F\u002F\u002F \u003Creturns>A task which will complete (or may already be completed) when the last participant calls this method.\u003C\u002Freturns>\n",[155,5872,5873],{"class":157,"line":2154},[155,5874,5875],{},"    public ValueTask SignalAndWait(CancellationToken cancellationToken)\n",[155,5877,5878],{"class":157,"line":2159},[155,5879,607],{},[155,5881,5882],{"class":157,"line":2165},[155,5883,5884],{},"        lock (this.waiters)\n",[155,5886,5887],{"class":157,"line":2171},[155,5888,1011],{},[155,5890,5891],{"class":157,"line":2176},[155,5892,5893],{},"            if (this.waiters.Count + 1 == this.participantCount)\n",[155,5895,5896],{"class":157,"line":2182},[155,5897,4820],{},[155,5899,5900],{"class":157,"line":2188},[155,5901,5902],{},"                \u002F\u002F This is the last one we were waiting for.\n",[155,5904,5905],{"class":157,"line":2193},[155,5906,5907],{},"                \u002F\u002F Unleash everyone that preceded this one.\n",[155,5909,5910],{"class":157,"line":2199},[155,5911,5912],{},"                while (this.waiters.Count > 0)\n",[155,5914,5915],{"class":157,"line":2205},[155,5916,5917],{},"                {\n",[155,5919,5920],{"class":157,"line":2210},[155,5921,5922],{},"                    Waiter waiter = this.waiters.Pop();\n",[155,5924,5925],{"class":157,"line":2215},[155,5926,5927],{},"                    waiter.CompletionSource.TrySetResult(default);\n",[155,5929,5930],{"class":157,"line":2221},[155,5931,5932],{},"                    waiter.CancellationRegistration.Dispose();\n",[155,5934,5935],{"class":157,"line":2226},[155,5936,5937],{},"                }\n",[155,5939,5940],{"class":157,"line":2231},[155,5941,517],{"emptyLinePlaceholder":516},[155,5943,5944],{"class":157,"line":2236},[155,5945,5946],{},"                \u002F\u002F And allow this one to continue immediately.\n",[155,5948,5949],{"class":157,"line":2242},[155,5950,5951],{},"                return new ValueTask(cancellationToken.IsCancellationRequested\n",[155,5953,5954],{"class":157,"line":2248},[155,5955,5956],{},"                    ? Task.FromCanceled(cancellationToken)\n",[155,5958,5959],{"class":157,"line":2253},[155,5960,5961],{},"                    : Task.CompletedTask);\n",[155,5963,5964],{"class":157,"line":2259},[155,5965,4830],{},[155,5967,5968],{"class":157,"line":2265},[155,5969,5970],{},"            else\n",[155,5972,5973],{"class":157,"line":2270},[155,5974,4820],{},[155,5976,5977],{"class":157,"line":2276},[155,5978,5979],{},"                \u002F\u002F We need more folks. So suspend this caller.\n",[155,5981,5982],{"class":157,"line":2281},[155,5983,5984],{},"                TaskCompletionSource\u003CEmptyStruct> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);\n",[155,5986,5987],{"class":157,"line":2286},[155,5988,5989],{},"                CancellationTokenRegistration ctr;\n",[155,5991,5992],{"class":157,"line":2291},[155,5993,5994],{},"                if (cancellationToken.CanBeCanceled)\n",[155,5996,5997],{"class":157,"line":2297},[155,5998,5917],{},[155,6000,6001],{"class":157,"line":2302},[155,6002,6003],{},"#if NET\n",[155,6005,6006],{"class":157,"line":2307},[155,6007,6008],{},"                    ctr = cancellationToken.Register(\n",[155,6010,6011],{"class":157,"line":2312},[155,6012,6013],{},"                        static (tcs, ct) => ((TaskCompletionSource\u003CEmptyStruct>)tcs!).TrySetCanceled(ct), tcs);\n",[155,6015,6016],{"class":157,"line":2318},[155,6017,6018],{},"#else\n",[155,6020,6021],{"class":157,"line":2324},[155,6022,6008],{},[155,6024,6025],{"class":157,"line":2330},[155,6026,6027],{},"                        static s =>\n",[155,6029,6030],{"class":157,"line":2335},[155,6031,6032],{},"                        {\n",[155,6034,6035],{"class":157,"line":2340},[155,6036,6037],{},"                            var t = (Tuple\u003CTaskCompletionSource\u003CEmptyStruct>, CancellationToken>)s!;\n",[155,6039,6040],{"class":157,"line":2345},[155,6041,6042],{},"                            t.Item1.TrySetCanceled(t.Item2);\n",[155,6044,6045],{"class":157,"line":2350},[155,6046,6047],{},"                        },\n",[155,6049,6050],{"class":157,"line":2355},[155,6051,6052],{},"                        Tuple.Create(tcs, cancellationToken));\n",[155,6054,6055],{"class":157,"line":2361},[155,6056,6057],{},"#endif\n",[155,6059,6060],{"class":157,"line":2367},[155,6061,5937],{},[155,6063,6064],{"class":157,"line":2373},[155,6065,6066],{},"                else\n",[155,6068,6069],{"class":157,"line":2379},[155,6070,5917],{},[155,6072,6073],{"class":157,"line":2384},[155,6074,6075],{},"                    ctr = default;\n",[155,6077,6078],{"class":157,"line":2389},[155,6079,5937],{},[155,6081,6082],{"class":157,"line":2394},[155,6083,517],{"emptyLinePlaceholder":516},[155,6085,6086],{"class":157,"line":2400},[155,6087,6088],{},"                this.waiters.Push(new Waiter(tcs, ctr));\n",[155,6090,6091],{"class":157,"line":2405},[155,6092,6093],{},"                return new ValueTask(tcs.Task);\n",[155,6095,6096],{"class":157,"line":2411},[155,6097,4830],{},[155,6099,6100],{"class":157,"line":2417},[155,6101,1031],{},[155,6103,6104],{"class":157,"line":2423},[155,6105,656],{},[155,6107,6108],{"class":157,"line":2428},[155,6109,517],{"emptyLinePlaceholder":516},[155,6111,6112],{"class":157,"line":2433},[155,6113,6114],{},"    private readonly struct Waiter(TaskCompletionSource\u003CEmptyStruct> completionSource, CancellationTokenRegistration cancellationRegistration)\n",[155,6116,6117],{"class":157,"line":2438},[155,6118,607],{},[155,6120,6121],{"class":157,"line":2444},[155,6122,6123],{},"        internal readonly TaskCompletionSource\u003CEmptyStruct> CompletionSource => completionSource;\n",[155,6125,6126],{"class":157,"line":2450},[155,6127,517],{"emptyLinePlaceholder":516},[155,6129,6130],{"class":157,"line":2455},[155,6131,6132],{},"        internal readonly CancellationTokenRegistration CancellationRegistration => cancellationRegistration;\n",[155,6134,6135],{"class":157,"line":2460},[155,6136,656],{},[155,6138,6139],{"class":157,"line":2466},[155,6140,661],{},[155,6142,6143],{"class":157,"line":2472},[155,6144,517],{"emptyLinePlaceholder":516},[155,6146,6147],{"class":157,"line":2477},[155,6148,5664],{},[155,6150,6151],{"class":157,"line":2482},[155,6152,6153],{},"\u002F\u002F\u002F An empty struct.\n",[155,6155,6156],{"class":157,"line":2488},[155,6157,5674],{},[155,6159,6160],{"class":157,"line":2493},[155,6161,6162],{},"\u002F\u002F\u002F \u003Cremarks>\n",[155,6164,6165],{"class":157,"line":2498},[155,6166,6167],{},"\u002F\u002F\u002F This can save 4 bytes over System.Object when a type argument is required for a generic type, but entirely unused.\n",[155,6169,6170],{"class":157,"line":2503},[155,6171,6172],{},"\u002F\u002F\u002F \u003C\u002Fremarks>\n",[155,6174,6175],{"class":157,"line":2508},[155,6176,6177],{},"internal readonly struct EmptyStruct\n",[155,6179,6180],{"class":157,"line":2514},[155,6181,565],{},[155,6183,6184],{"class":157,"line":2520},[155,6185,5688],{},[155,6187,6188],{"class":157,"line":2525},[155,6189,6190],{},"    \u002F\u002F\u002F Gets an instance of the empty struct.\n",[155,6192,6193],{"class":157,"line":2531},[155,6194,5698],{},[155,6196,6197],{"class":157,"line":2537},[155,6198,6199],{},"    internal static EmptyStruct Instance\n",[155,6201,6202],{"class":157,"line":2543},[155,6203,607],{},[155,6205,6206],{"class":157,"line":2548},[155,6207,6208],{},"        get { return default(EmptyStruct); }\n",[155,6210,6211],{"class":157,"line":2554},[155,6212,656],{},[155,6214,6215],{"class":157,"line":2559},[155,6216,661],{},[14,6218,6219],{},"使用方法：",[145,6221,6223],{"className":546,"code":6222,"language":548,"meta":150,"style":150},"public partial class MainViewModel : ObservableObject\n{\n    public ObservableCollection\u003Cstring> Results { get; } = [];\n    private readonly AsyncBarrier _asyncBarrier = new(3);\n    [RelayCommand]\n    async Task FirstJobAsync(CancellationToken cancellationToken)\n    {\n        await Task.Delay(1500, cancellationToken);\n        Results.Add(\"first job completed.Waiting for others..\");\n        await _asyncBarrier.SignalAndWait(cancellationToken);\n        Results.Add(\"first job done!\");\n    }\n    [RelayCommand]\n    async Task SecondJobAsync(CancellationToken cancellationToken) {\n        await Task.Delay(1500, cancellationToken);\n        Results.Add($\"second job completed.Waiting for others\");\n        await _asyncBarrier.SignalAndWait(cancellationToken);\n        Results.Add(\"second job done!\");\n    }\n    [RelayCommand]\n    async Task ThirdJobAsync(CancellationToken cancellationToken)\n    {\n        await Task.Delay(1500, cancellationToken);\n        Results.Add($\"third job completed.Waiting for others\");\n        await _asyncBarrier.SignalAndWait(cancellationToken);\n        Results.Add(\"third job done!\");\n    }\n\n}\n\n",[152,6224,6225,6230,6234,6239,6244,6249,6254,6258,6263,6268,6273,6278,6282,6286,6291,6295,6300,6304,6309,6313,6317,6322,6326,6330,6335,6339,6344,6348,6352],{"__ignoreMap":150},[155,6226,6227],{"class":157,"line":158},[155,6228,6229],{},"public partial class MainViewModel : ObservableObject\n",[155,6231,6232],{"class":157,"line":164},[155,6233,565],{},[155,6235,6236],{"class":157,"line":170},[155,6237,6238],{},"    public ObservableCollection\u003Cstring> Results { get; } = [];\n",[155,6240,6241],{"class":157,"line":176},[155,6242,6243],{},"    private readonly AsyncBarrier _asyncBarrier = new(3);\n",[155,6245,6246],{"class":157,"line":182},[155,6247,6248],{},"    [RelayCommand]\n",[155,6250,6251],{"class":157,"line":188},[155,6252,6253],{},"    async Task FirstJobAsync(CancellationToken cancellationToken)\n",[155,6255,6256],{"class":157,"line":194},[155,6257,607],{},[155,6259,6260],{"class":157,"line":200},[155,6261,6262],{},"        await Task.Delay(1500, cancellationToken);\n",[155,6264,6265],{"class":157,"line":206},[155,6266,6267],{},"        Results.Add(\"first job completed.Waiting for others..\");\n",[155,6269,6270],{"class":157,"line":347},[155,6271,6272],{},"        await _asyncBarrier.SignalAndWait(cancellationToken);\n",[155,6274,6275],{"class":157,"line":353},[155,6276,6277],{},"        Results.Add(\"first job done!\");\n",[155,6279,6280],{"class":157,"line":358},[155,6281,656],{},[155,6283,6284],{"class":157,"line":363},[155,6285,6248],{},[155,6287,6288],{"class":157,"line":369},[155,6289,6290],{},"    async Task SecondJobAsync(CancellationToken cancellationToken) {\n",[155,6292,6293],{"class":157,"line":375},[155,6294,6262],{},[155,6296,6297],{"class":157,"line":381},[155,6298,6299],{},"        Results.Add($\"second job completed.Waiting for others\");\n",[155,6301,6302],{"class":157,"line":387},[155,6303,6272],{},[155,6305,6306],{"class":157,"line":393},[155,6307,6308],{},"        Results.Add(\"second job done!\");\n",[155,6310,6311],{"class":157,"line":399},[155,6312,656],{},[155,6314,6315],{"class":157,"line":405},[155,6316,6248],{},[155,6318,6319],{"class":157,"line":411},[155,6320,6321],{},"    async Task ThirdJobAsync(CancellationToken cancellationToken)\n",[155,6323,6324],{"class":157,"line":416},[155,6325,607],{},[155,6327,6328],{"class":157,"line":421},[155,6329,6262],{},[155,6331,6332],{"class":157,"line":426},[155,6333,6334],{},"        Results.Add($\"third job completed.Waiting for others\");\n",[155,6336,6337],{"class":157,"line":432},[155,6338,6272],{},[155,6340,6341],{"class":157,"line":438},[155,6342,6343],{},"        Results.Add(\"third job done!\");\n",[155,6345,6346],{"class":157,"line":444},[155,6347,656],{},[155,6349,6350],{"class":157,"line":450},[155,6351,517],{"emptyLinePlaceholder":516},[155,6353,6354],{"class":157,"line":455},[155,6355,661],{},[289,6357,6358],{"id":6358},"启动后自动执行异步任务",[6360,6361,6362],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":150,"searchDepth":164,"depth":164,"links":6364},[6365,6366],{"id":12,"depth":164,"text":12},{"id":287,"depth":164,"text":287,"children":6367},[6368,6369,6370,6371,6372,6373,6374,6375,6376,6377,6378,6379],{"id":291,"depth":170,"text":291},{"id":1478,"depth":170,"text":1478},{"id":1875,"depth":170,"text":1876},{"id":2667,"depth":170,"text":2668},{"id":3078,"depth":170,"text":3078},{"id":3087,"depth":170,"text":3087},{"id":3602,"depth":170,"text":3602},{"id":4705,"depth":170,"text":4705},{"id":5153,"depth":170,"text":5154},{"id":5431,"depth":170,"text":5431},{"id":5584,"depth":170,"text":5585},{"id":6358,"depth":170,"text":6358},"md",{},"\u002Fcsharp\u002Fc的多线程",{"description":150},"csharp\u002FC#的多线程","aKRpowk-_Jwa4ZEL6zQR5MDkAOE8D80vZfeAlAIVRnk",1791042464990]