เปิด analyzer ครบทุกตัวแล้ว await ใน foreach ก็ยังผ่านด่านโดยไม่มีเสียงเตือน
คุณเขียน foreach ที่ยิง HTTP ทีละรายการ แล้วเปิด pull request
ไม่มีใครทัก บิลด์เขียว analyzer เงียบ CI ผ่าน หน้านั้นขึ้น production แล้วช้ากว่าที่ควร เป็นเท่าตัว จนกว่าจะมีคนไปเปิดดูเอง
ความเชื่อที่ทำให้เรื่องนี้เกิดซ้ำได้เรื่อย ๆ คือ “ถ้ามันแย่จริง เครื่องมือคงเตือนแล้ว” หน้านี้เอาความเชื่อนั้นไปวัด
ผลคือเครื่องมือ มีอะไรจะพูด กับ code นั้นเยอะมาก มันพ่น 17 diagnostic ใส่ file สองใบที่มีตัววน และในนั้น 0 ตัวที่พูดถึงตัววน
วิธีวัด เปิดทุกสวิตช์ที่ SDK ยอมให้ตั้ง แล้วนับสิ่งที่มันพูด
หัวข้อที่มีชื่อว่า “วิธีวัด เปิดทุกสวิตช์ที่ SDK ยอมให้ตั้ง แล้วนับสิ่งที่มันพูด”ข้ออ้างว่า “ไม่มีใครเตือน” ไม่มีความหมายเลย ถ้าเราเป็นคนปิดปากเครื่องมือเอง
ตัววัดบิลด์ file สี่ใบใน compilation เดียวกัน ด้วยสวิตช์ชุดเดียวกัน บน SDK 10.0.302
| file | มีอะไรอยู่ในนั้น | หน้าที่ |
|---|---|---|
Sequential.cs | await ใน foreach for และ while ปลายทางเป็น delegate | ตัวที่ถูกวัด |
Realistic.cs | รูปเดียวกัน ปลายทางเป็น HttpClient | ตัวที่ถูกวัด |
Control.cs | ของที่ผิดในเรื่องที่เครื่องมือจับได้แน่ ๆ | ช่องควบคุม |
Nearby.cs | stackalloc ในตัววน | rule ที่เข้าใกล้ที่สุด |
สวิตช์ที่เปิดคือ AnalysisLevel=latest-all AnalysisMode=All EnforceCodeStyleInBuild
และ WarningLevel=9999 ซึ่งเป็นชุดที่แรงที่สุดที่ตั้งได้โดยไม่ต้องลง package เพิ่ม
ในบิลด์นั้นมี analyzer ทำงานจริง 377 ตัว ครอบคลุม rule id 447 ตัว
“ไม่มี diagnostic” เป็นผลที่ได้ฟรีถ้าเครื่องมือไม่ได้ทำงานตั้งแต่แรก Control.cs จึงต้อง
ส่งเสียงทั้งสองชั้นในบิลด์เดียวกันนั้น
ชั้น compiler คือ CS4014 — เรียกของที่คืน Task ในสาย async แล้วทิ้งผลไป
ชั้น analyzer คือ CA1849 — บล็อกด้วย .Result ในสาย async
ถ้าตรวจแค่ชั้นเดียว อีกชั้นดับไปทั้งชั้นแล้วเราจะไม่รู้เลย และจะรายงานความเงียบนั้น เป็นข้อค้นพบ ทั้งที่มันคือเครื่องมือวัดพัง
เครื่องมือมีอะไรจะพูดเยอะมาก และไม่มีข้อไหนเป็นเรื่องตัววน
หัวข้อที่มีชื่อว่า “เครื่องมือมีอะไรจะพูดเยอะมาก และไม่มีข้อไหนเป็นเรื่องตัววน”ทุก diagnostic ที่ออกมาเป็นเรื่องอื่นทั้งหมด และหนึ่งในนั้นชี้ตรงที่
awaitตัวที่เป็นปัญหาพอดี
diagnostic ทั้ง 17 ตัวที่ตกบน file สองใบนั้น แยกได้เป็นสาม rule
| rule | กี่ตัว | มันบ่นเรื่องอะไร |
|---|---|---|
CA1062 | 10 | ยังไม่ได้ตรวจว่า parameter เป็น null หรือไม่ |
CA2007 | 5 | ควรเรียก ConfigureAwait ต่อท้าย task ที่ await |
CA2234 | 2 | ควรส่ง Uri แทน string |
CA2007 คือตัวที่น่าสนใจที่สุดในตาราง มันไม่ได้บ่นแบบเหมารวมทั้ง method
มันชี้ไปที่ token await ตัวนั้นเป๊ะ ตัวเดียวกับที่ทำให้ทั้งตัววนเดินทีละรอบ
ข้อความของมันคือ "Consider calling ConfigureAwait on the awaited task"
เครื่องมือเห็น await ตัวนั้น มีความเห็นเรื่อง ConfigureAwait ของมัน แล้วไม่มีความเห็น
เรื่อง foreach ที่ครอบมันอยู่
ตั้ง warning เป็น error แล้วบิลด์ก็ล้ม แต่ล้มด้วยเรื่องอื่น
หัวข้อที่มีชื่อว่า “ตั้ง warning เป็น error แล้วบิลด์ก็ล้ม แต่ล้มด้วยเรื่องอื่น”เพิ่ม TreatWarningsAsErrors=true เข้าไปในโปรเจกต์ที่มีแต่ file สองใบนั้น บิลด์ล้มทันที
ด้วย 3 rule คือ CA1062 · CA2007 · CA2234
ไม่มีตัวไหนในนั้นเกี่ยวกับตัววนเลย ทั้งสามเป็นเรื่อง null เรื่อง ConfigureAwait
และเรื่อง Uri
ทีนี้ปิดเสียงเฉพาะสามตัวนั้น ซึ่งเป็นสิ่งที่ทีมจริงทำกันเป็นปกติ เพราะมันไม่เกี่ยวกับ บั๊กที่เรากำลังหา แล้ววัดซ้ำ
dotnet build --no-incremental \ -p:TreatWarningsAsErrors=true \ -p:NoWarn="CA1062;CA2007;CA2234"บิลด์ผ่าน เหลือ 0 diagnostic
code ที่ยิงเน็ตทีละรายการติดต่อกัน ผ่านด่านที่แรงที่สุดที่ .NET ตั้งได้ โดยสะอาดหมดจด
เครื่องมือมองเห็นตัววนได้ มันแค่ไม่มี rule สำหรับรูปนี้
หัวข้อที่มีชื่อว่า “เครื่องมือมองเห็นตัววนได้ มันแค่ไม่มี rule สำหรับรูปนี้”ความเงียบนี้ไม่ได้แปลว่า analyzer อ่านตัววนไม่ออก มี rule ที่อ่านออกอยู่ และมันดังในบิลด์เดียวกัน
ข้อสรุปที่ง่ายเกินไปคือ “analyzer มันโง่” ซึ่งไม่จริง และตัววัดพิสูจน์ว่าไม่จริง
Nearby.cs มี stackalloc อยู่ในตัววน แล้ว CA2014 ก็ดังในบิลด์เดียวกันนั้น
ข้อความของมันคือ "Potential stack overflow. Move the stackalloc out of the loop."
rule ตัวนั้นอ่านออกว่าอะไรอยู่ในตัววน ความสามารถนั้นมีอยู่แล้วในกล่อง สิ่งที่ไม่มีคือ
rule ที่เอาความสามารถนั้นมาส่องดู await
ใน roster มี CA1842 กับ CA1843 ซึ่งตั้งชื่อตาม Task.WhenAll จริง
แต่เปิดเอกสารดูแล้วมันคนละเรื่องกับหน้านี้
ช่อง Cause ของ CA1842 เขียนว่า “WhenAll is called with a single task”
และหมวดของมันคือ Performance มันคือ rule ที่ดักตอนคุณเรียก WhenAll ด้วย task
ตัวเดียว ซึ่งเป็นความซ้ำซ้อน
มันดักกรณีที่คุณ ใช้ WhenAll เกินจำเป็น ไม่ได้ดักกรณีที่คุณ ไม่ได้ใช้ มันทั้งที่ควรใช้
สองอย่างนี้เป็นคนละคำถาม และในกล่องมีแต่อย่างแรก
ตัวเลขทุกตัวข้างบนวัดจาก .NET SDK ตามที่มันมาในกล่อง ไม่ได้ลง analyzer package ของเจ้าอื่นเพิ่ม การวัดนี้จึงไม่ได้พูดแทน Roslynator, SonarAnalyzer หรือ package อื่นที่คุณลงเองได้
และจำนวน diagnostic ขึ้นกับรุ่นของ SDK เพราะ Microsoft เพิ่ม rule ใหม่ทุกรุ่น ตัวเลขในหน้านี้จึงมาพร้อมป้าย 10.0.302 เสมอ ส่วนด่านที่ CI รันตรวจ รูปของผล ไม่ได้ตรึงจำนวนไว้ทีละตัว
และรูปของผลนั้นข้าม SDK ได้จริง ด่านเดียวกันนี้รันบน runner ของ CI ที่ใช้ SDK 10.0.400 ซึ่งมี analyzer 378 ตัวและ rule id 448 ตัว มากกว่าเครื่องผู้เขียนอย่างละหนึ่ง
ผลที่ได้คือ 17 diagnostic และ 0 ตัวที่พูดถึงตัววน ด้วย rule id ชุดเดิมทั้งสามตัว
ราคาที่ความเงียบนั้นปล่อยผ่าน
หัวข้อที่มีชื่อว่า “ราคาที่ความเงียบนั้นปล่อยผ่าน”8 รายการ รายการละ 60 มิลลิวินาที ต่างกัน 8 เท่า
ตัววัดยิงงานที่ใช้เวลา 60 ms เท่ากันทุกตัว จำนวน 8 ตัว
ปลายทางเป็น Task.Delay ล้วน ไม่ใช่งาน CPU เพราะสิ่งที่วัดคือ เวลาที่รอต่อกันเป็นทอด ๆ
ไม่ใช่ปริมาณงาน
| ท่า | เวลาที่ใช้ |
|---|---|
await ใน foreach | 489 ms |
await Task.WhenAll(...) | 61 ms |
ฝั่ง foreach ใช้เวลาเท่ากับผลรวมของทุกตัว คือราว 480 ms ส่วนฝั่ง WhenAll
ใช้เวลาเท่ากับตัวที่ช้าที่สุดตัวเดียว
ตัวเลขนี้เป็นของเครื่องที่รัน (.NET 10.0.10) สิ่งที่ไม่ขึ้นกับเครื่องคืออัตราส่วน
ทางแก้ที่ทุกคนบอก เปลี่ยนพฤติกรรมของ exception ไปด้วย
หัวข้อที่มีชื่อว่า “ทางแก้ที่ทุกคนบอก เปลี่ยนพฤติกรรมของ exception ไปด้วย”
await Task.WhenAllโยน exception ตัวแรกตัวเดียว ไม่ใช่AggregateException
ครึ่งนี้สำคัญกว่าครึ่งแรก เพราะมันเปลี่ยนสิ่งที่คุณเห็นตอนของพัง
ตัววัดสร้าง task ที่ fault พร้อมกัน 5 ตัว แล้ว await Task.WhenAll
ทับมัน 2,000 รอบ
| สิ่งที่ถาม | ผลจาก 2,000 รอบ |
|---|---|
await โยนอะไรออกมา | InvalidOperationException |
กี่รอบที่ได้ AggregateException | 0 |
property .Exception ของ task เป็นอะไร | AggregateException |
ใน .Exception มี inner กี่ตัว | 5 |
.Wait() โยนอะไร | AggregateException |
AggregateException มีอยู่จริง และมันครบทั้ง 5 ตัว แต่มันอยู่ใน
property .Exception ของ task ไม่ได้ถูกโยนออกมาทาง await
await แกะ AggregateException ออกแล้วโยนตัวแรกใบเดียว นี่คือพฤติกรรมที่ตั้งใจ
ไม่ใช่บั๊ก แต่ผลของมันคือ
try{ await Task.WhenAll(tasks);}catch (AggregateException ex) // ❌ block นี้ไม่เคยทำงาน{ Log(ex.InnerExceptions);}catch ก้อนนั้นไม่เคยเข้าเลยสักรอบจาก 2,000 รอบ คนเขียนเห็นของพัง
1 ใน 5 แล้วสรุปว่ามีปัญหาจุดเดียว
ถ้าอยากได้ครบทุกตัว ต้องหยิบจาก task โดยตรง
var all = Task.WhenAll(tasks);try{ await all;}catch{ // all.Exception คือ AggregateException ที่ครบทุกตัว Log(all.Exception!.InnerExceptions);}Task.WhenAll รอให้ครบทุกตัวจริง ก่อนที่ความผิดพลาดจะออกมา ตัววัดยืนยันว่างานทั้ง
5 ตัวจบก่อน exception จะโผล่
ประโยคที่ผิดคือ “แล้วค่อยโยน AggregateException” ไม่ใช่ “รอครบก่อน”
WhenAll ไม่ใช่คำตอบเสมอไป และนี่คือสามกรณีที่มันทำ code พัง
หัวข้อที่มีชื่อว่า “WhenAll ไม่ใช่คำตอบเสมอไป และนี่คือสามกรณีที่มันทำ code พัง”หน้าที่บอกว่า “ใช้
WhenAllสิ” ทุกครั้ง ผิดพอ ๆ กับความเชื่อที่หน้านี้เพิ่งรื้อทิ้ง
await ในตัววนไม่ได้เป็นบั๊กโดยตัวมันเอง มันเป็นบั๊กเมื่องานแต่ละชิ้น ไม่เกี่ยวข้องกัน
เมื่อไรที่มันเกี่ยวข้องกัน การย้ายไป WhenAll คือการเปลี่ยนผลลัพธ์ ไม่ใช่การเร่งความเร็ว
สองกรณีแรกข้างล่างเป็น ผลแน่นอน ตัวเลขออกมาเป็น 0 กับเต็มจำนวนทุกช่อง ทุกรอบ และเหมือนกันบนทุกเครื่องที่วัด
กรณีที่สามไม่ใช่ และนั่นคือกรณีที่อันตรายกว่า
หนึ่ง เมื่อลำดับคือความหมาย
หัวข้อที่มีชื่อว่า “หนึ่ง เมื่อลำดับคือความหมาย”ตัววัดสั่งงาน 5 ชิ้น โดยให้ชิ้นแรกช้าที่สุด ไล่ลงมาเป็น 100 · 80 · 60 · 40 · 20 มิลลิวินาที แล้วบันทึกลำดับที่แต่ละชิ้นจบ ทำซ้ำ 10 รอบ
| ท่า | จำนวนรอบที่ลำดับยังเหมือนที่สั่ง |
|---|---|
await ใน foreach | 10 จาก 10 |
await Task.WhenAll(...) | 0 จาก 10 |
ฝั่ง WhenAll จบเรียงตาม ระยะเวลาของงาน ซึ่งในการวัดนี้คือกลับหัวพอดี
ถ้าตัววนของคุณเขียน audit log ตัดสต็อกตามลำดับคิว หรือส่ง event ที่ปลายทางเรียงลำดับ
foreach คือคำตอบที่ถูก ไม่ใช่บั๊ก
สอง เมื่อของที่ใช้ร่วมกันปฏิเสธการถูกใช้ซ้อน
หัวข้อที่มีชื่อว่า “สอง เมื่อของที่ใช้ร่วมกันปฏิเสธการถูกใช้ซ้อน”ทรัพยากรจำนวนมากไม่ยอมให้ใช้ซ้อนกัน แล้วโยนทิ้งทันที ตัววัดใช้ยามที่เขียนเองเพื่อให้ผล เป็นของแน่นอน ทำซ้ำ 200 รอบ
| ท่า | จำนวนรอบที่ชนยาม |
|---|---|
await ใน foreach | 0 จาก 200 |
await Task.WhenAll(...) | 200 จาก 200 |
ตัวที่คนเขียน .NET เจอบ่อยที่สุดคือ DbContext ของ EF Core เอกสารของมันเขียนไว้ตรงตัว
ว่า EF Core ไม่รองรับ operation หลายตัวที่วิ่งขนานกันบน DbContext ตัวเดียว
เมื่อมันตรวจเจอ คุณจะได้ InvalidOperationException ที่ขึ้นต้นว่า “A second operation
started on this context before a previous operation completed”
ตัววนที่เรียก repository ตัวเดียวกันทีละรายการ จึงเป็น code ที่ถูกอยู่แล้ว การเปลี่ยนเป็น
WhenAll โดยไม่แตะอย่างอื่น คือการแลกความช้าที่เห็นได้ กับ exception ตอนตีสาม
สาม เมื่อของที่ใช้ร่วมกันไม่มียามเลย
หัวข้อที่มีชื่อว่า “สาม เมื่อของที่ใช้ร่วมกันไม่มียามเลย”กรณีที่แย่กว่ากรณีที่สอง คือกรณีที่ไม่มีใครโยนอะไรให้เห็น
List<T> ไม่ปลอดภัยต่อ thread และไม่มีอะไรตรวจให้ ตัววัดเขียนลง list ใบเดียวกัน
2,000 รายการ ทำซ้ำ 20 รอบ
ตารางนี้ต่างจากสองตารางบน เพราะมันมีสองเครื่อง
| เครื่อง | foreach ผิดกี่รอบ | WhenAll ผิดกี่รอบ |
|---|---|---|
เครื่องผู้เขียน · Arm64 · 18 core · SDK 10.0.302 | 0 จาก 20 | 20 จาก 20 |
| runner ของ CI · ubuntu-latest (x64) · SDK 10.0.400 | 0 จาก 20 | 0 จาก 20 |
code ชุดเดียวกัน ข้อมูลชุดเดียวกัน คำสั่งเดียวกัน ฝั่ง WhenAll พังทุกรอบบนเครื่องหนึ่ง
และไม่พังเลยสักรอบบนอีกเครื่องหนึ่ง
ฝั่ง foreach ถูกทุกรอบทั้งสองเครื่อง เพราะมันไม่มี concurrency ให้ผิดตั้งแต่แรก
ตารางบนเป็นตัวเลขจริงจากรันจริงสองเครื่อง ไม่ใช่การเปรียบเปรย
บั๊กชนิดนี้ผ่าน code review ได้ ผ่านเทสต์ในเครื่องได้ ผ่าน CI ได้ครบทุกด่าน แล้วไปโผล่ ที่ production ซึ่งมีคอร์มากกว่าและมีโหลดจริง จำนวนคอร์กับจังหวะของ scheduler เป็นตัวตัดสินว่าคุณจะเห็นมันวันนี้หรืออีกหกเดือน
สังเกตว่ากรณีที่สอง — ทรัพยากรที่มียาม — โยน exception ทุกรอบทั้งสองเครื่อง ยามตัวนั้นคือของขวัญ มันเปลี่ยนบั๊กที่ซ่อนตัวได้ ให้เป็นบั๊กที่ต้องแก้วันนี้
ของที่ไม่มียาม ไม่ได้ปลอดภัยกว่า มันแค่เงียบกว่า
ผลผิดในที่นี้คือจำนวนสมาชิกไม่ครบ หรือ list โยน exception ออกมากลางทาง ทั้งสองแบบ ไม่มี diagnostic ตัวไหนเตือนล่วงหน้าเช่นกัน
ตอนแรกตัววัดบังคับว่าฝั่ง WhenAll ต้องผิดอย่างน้อยหนึ่งรอบ ถึงจะถือว่าผ่าน
มันผ่านบนเครื่องผู้เขียน แล้วทำให้ CI แดงทันทีที่รันบน runner ตัวจริง — ไม่ใช่เพราะ ข้ออ้างผิด แต่เพราะ race ไม่มีวันนิ่งพอจะเป็นเงื่อนไขของด่าน
ตอนนี้ตัวเลขฝั่ง WhenAll ถูกรายงานและถูกเก็บลง file ข้อมูล แต่ไม่ได้ตัดสินว่าด่าน
เขียวหรือแดง ส่วนฝั่ง foreach ที่เป็นผลแน่นอนจริง ยังเป็นเงื่อนไขอยู่
แล้วควรทำอย่างไร
หัวข้อที่มีชื่อว่า “แล้วควรทำอย่างไร”คำถามที่ต้องตอบก่อนแก้ ไม่ใช่ “เร็วขึ้นไหม” แต่คือ “งานแต่ละชิ้นเกี่ยวข้องกันหรือเปล่า”
ถามสามข้อนี้กับตัววนที่กำลังจะแก้
ลำดับของการเรียกมีความหมายไหม ถ้ามี ปล่อย foreach ไว้ แล้วเขียนคอมเมนต์บอกว่า
ทำไม เพราะคนถัดไปจะมาแก้เป็น WhenAll ให้คุณ
ตัว loop body แตะของที่ใช้ร่วมกันหรือเปล่า ถ้าแตะ ต้องแยกของนั้นให้เป็นคนละตัวต่อ
งานหนึ่งชิ้นก่อน ไม่ใช่แค่ห่อด้วย WhenAll
ปลายทางรับ concurrency ได้แค่ไหน WhenAll ที่จำนวนงานมาจาก input ของผู้ใช้ คือ
การเปิดให้ใครก็ได้สั่งยิงปลายทางกี่ครั้งก็ได้ในคำขอเดียว เพดานมาตรฐานคือ SemaphoreSlim
เมื่อทั้งสามข้อผ่านแล้ว WhenAll คือคำตอบ และอย่าลืมว่า catch (AggregateException)
รอบมันไม่ทำงาน
ข้อสรุปที่ตัวเลขชุดนี้รองรับจริง ๆ
หัวข้อที่มีชื่อว่า “ข้อสรุปที่ตัวเลขชุดนี้รองรับจริง ๆ”หน้านี้ไม่ได้จบด้วย “ใช้ WhenAll สิ” เพราะตัวเลขไม่ได้บอกอย่างนั้น
ตัวเลขบอกว่า ไม่มีใครเตือนคุณทั้งสองทาง ตอนคุณเขียน foreach ที่ควรจะขนาน ไม่มี
diagnostic ตัวไหนดัง และตอนคุณเปลี่ยนเป็น WhenAll ทับตัววนที่ลำดับมีความหมาย
หรือที่ loop body แตะของร่วมกัน ก็ไม่มี diagnostic ตัวไหนดังเหมือนกัน
เครื่องมือไม่ได้อยู่ข้างใดข้างหนึ่ง มันไม่อยู่ในบทสนทนานี้เลย คนที่ต้องรู้ว่างานแต่ละชิ้น เกี่ยวข้องกันหรือไม่ คือคนที่เขียนมัน และนั่นคือเหตุผลที่ต้องเขียนคอมเมนต์ทิ้งไว้
คอร์ส Rust async บทที่ 7 เคยเขียนว่า “ต่างกันแค่ว่าใน C# บางครั้ง analyzer ยังพอเตือน ได้บ้าง” และเขียนซ้ำอีกครั้งในตารางเทียบ C# กับ Rust ท้ายบท
ไม่มีใครวัดประโยคนั้นก่อนเขียน และหน้านี้คือการวัดมัน ผลคือประโยคนั้นผิด — ฝั่ง C# ไม่ได้ “พอเตือนได้บ้าง” มันเงียบสนิทเท่ากับฝั่ง Rust ต่างกันที่ฝั่ง C# มี rule อยู่ใกล้ ๆ สองตัวที่ไม่ได้ต่อกัน
บทเรียนนั้นถูกแก้ตามผลการวัดนี้แล้ว
รันซ้ำเองได้
หัวข้อที่มีชื่อว่า “รันซ้ำเองได้”สองคำสั่ง หนึ่งวัดใหม่ทั้งชุด อีกหนึ่งพิสูจน์ว่าตัวตรวจแดงได้จริง
ตัววัดทั้งชุดใช้ .NET SDK อย่างเดียว ไม่ต้องมี Docker ไม่ต้องมีเน็ต และไม่มี
PackageReference สักตัว
# วัดใหม่ทั้งชุดแล้วตรวจข้ออ้างของหน้านี้ — อยู่ในเกต unit ของ CIpython3 scripts/await-foreach/probe.py --check
# พิสูจน์ว่าด่านแดงได้ ยิงข้อมูลที่ทำให้ผิดทีละแบบใส่ตัวตรวจpython3 scripts/await-foreach/probe.py --selftest--check ไม่ได้เทียบกับตัวเลขที่ commit ไว้เฉย ๆ มันบิลด์ใหม่ รันใหม่ แล้วตรวจว่ารูป
ของผลยังเป็นจริงไหม — ช่องควบคุมยังดังทั้งสองชั้น CA2014 ยังดัง
จำนวน diagnostic ที่พูดถึงตัววนยังเป็น 0 และ await Task.WhenAll
ยังไม่โยน AggregateException
ตัวเลขฝั่ง Task.WhenAll ไม่ได้วัดใหม่ที่นี่ มันมาจาก scripts/whenall/ ซึ่งเป็น harness
ที่คอร์ส Rust async ใช้อยู่แล้ว probe รันโปรเจกต์นั้นแล้วอ่านผลของมัน เพื่อไม่ให้ตัวเลข
สองชุดค่อย ๆ ห่างกันโดยไม่มีอะไรฟ้อง
- CA2007 — Do not directly await a Task —
rule ที่ชี้ตรงที่
awaitตัวที่เป็นปัญหา แต่พูดเรื่องConfigureAwait(ตรวจแล้ว 2026-08-22) - CA2014 — Do not use stackalloc in loops — หลักฐานว่า analyzer อ่านออกว่าอะไรอยู่ในตัววน (ตรวจแล้ว 2026-08-22)
- CA1842 — Do not use WhenAll with a single task —
rule ที่ตั้งชื่อตาม
Task.WhenAllซึ่งอยู่ใน roster (ตรวจแล้ว 2026-08-22) - Configure code analysis rules —
ความหมายของ
AnalysisLevelและAnalysisMode(ตรวจแล้ว 2026-08-22) - Task.WhenAll — พฤติกรรมของ task ที่คืนออกมาเมื่อมีตัวที่ fault (ตรวจแล้ว 2026-08-22)
- EF Core — Avoiding DbContext threading issues — ที่มาของข้อความ “A second operation started on this context” (ตรวจแล้ว 2026-08-22)