// MySql.Data, Version=9.1.0.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d // MySql.Data.MySqlClient.MySqlConnection using System.Threading; public override void Open() { OpenAsync(execAsync: false, CancellationToken.None).GetAwaiter().GetResult(); } // MySql.Data, Version=9.1.0.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d // MySql.Data.MySqlClient.MySqlCommand using System.Data; using System.Threading; public new MySqlDataReader ExecuteReader() { return ExecuteReaderAsync(CommandBehavior.Default, execAsync: false, CancellationToken.None).GetAwaiter().GetResult(); } public override object ExecuteScalar() { return ExecuteScalarAsync(execAsync: false, CancellationToken.None).GetAwaiter().GetResult(); }仔细看上面这段代码,不觉让人吸了一口凉气,所谓的同步方式竟然是用异步方法简单包装 而来的,这种异步混用同步的方式很容易导致线程饥饿,即线程池中已无可用线程来唤醒 GetResult() 下的 Event 事件,这个我准备后面用一篇文章详细来聊一下线程饥饿,这里用C#内功修炼训练营中的一张图来演示下.NET8 中异步在线程池中的走法。
0:000> !tp Using the Portable thread pool. CPU utilization: 1% Workers Total: 268 Workers Running: 268 Workers Idle: 0 Worker Min Limit: 4 Worker Max Limit: 32767 0:000> !sos tpq global work item queue________________________________ 0x000002410E750218 Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT<Microsoft.AspNetCore.Hosting.HostingApplication+Context> 0x000002410E7505A0 Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT<Microsoft.AspNetCore.Hosting.HostingApplication+Context> 0x000002410E750928 Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT<Microsoft.AspNetCore.Hosting.HostingApplication+Context> ... local per thread work items_____________________________________ 0x0000024114903310 System.Runtime.CompilerServices.AsyncTaskMethodBuilder<MySql.Data.MySqlClient.MySqlPool>+AsyncStateMachineBox<MySql.Data.MySqlClient.MySqlPoolManager+<GetPoolAsync>d__23>
0:287> !clrstack OS Thread Id: 0x39ec (287) Child SP IP Call Site 000000858C5FD1B8 00007ffc95ca04e4 [HelperMethodFrame_1OBJ: 000000858c5fd1b8] System.Threading.Monitor.ObjWait(Int32, System.Object) 000000858C5FD2E0 00007ffc087cccc9 System.Threading.Monitor.Wait(System.Object, Int32) [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Monitor.CoreCLR.cs @ 156] 000000858C5FD310 00007ffc087cd027 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs @ 561] 000000858C5FD3D0 00007ffc087cc4f2 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3072] 000000858C5FD440 00007ffc087cc099 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3007] 000000858C5FD4C0 00007ffc08796cc6 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task, System.Threading.Tasks.ConfigureAwaitOptions) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs @ 111] 000000858C5FD500 00007ffc086ffbc4 xxxx.UpdateAnswerUrl(System.String, Int32, System.Collections.Generic.Dictionary`2<System.String,System.String>)发现这些线程都卡在 xxxx.UpdateAnswerUrl 方法上,那到底卡在方法的何处呢?可以用 !U /d 00007ffc086ffbc4 观察方法的反汇编代码,看看这个00007ffc086ffbc4停留在何处?输出如下:
0:000> !U /d 00007ffc086ffbc4 Normal JIT generated code xxx.UpdateAnswerUrl(System.String, Int32, System.Collections.Generic.Dictionary`2<System.String,System.String>) ... 00007ffc`086ffb79 ff15114bb9fe call qword ptr [00007ffc`07294690] (System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[[MySql.Data.MySqlClient.MySqlCommand+<ExecuteScalarAsync>d__117, MySql.Data]](<ExecuteScalarAsync>d__117 ByRef), mdToken: 000000000600646B) 00007ffc`086ffb7f 488b8c2468010000 mov rcx,qword ptr [rsp+168h] 00007ffc`086ffb87 4885c9 test rcx,rcx 00007ffc`086ffb8a 0f84890c0000 je 00007ffc`08700819 00007ffc`086ffb90 3809 cmp byte ptr [rcx],cl 00007ffc`086ffb92 48898c2498010000 mov qword ptr [rsp+198h],rcx 00007ffc`086ffb9a 488d8c2498010000 lea rcx,[rsp+198h] 00007ffc`086ffba2 48baf02b5006fc7f0000 mov rdx,7FFC06502BF0h (MT: System.Runtime.CompilerServices.TaskAwaiter`1[[System.Object, System.Private.CoreLib]]) 00007ffc`086ffbac ff158e7cdefd call qword ptr [00007ffc`064e7840] (System.Runtime.CompilerServices.TaskAwaiter`1[[System.__Canon, System.Private.CoreLib]].GetResult(), mdToken: 00000000060065F0) 00007ffc`086ffbb2 48898424e8000000 mov qword ptr [rsp+0E8h],rax 00007ffc`086ffbba eb0d jmp 00007ffc`086ffbc9 00007ffc`086ffbbc 33d2 xor edx,edx 00007ffc`086ffbbe ff1544d4bffd call qword ptr [00007ffc`062fd008] (System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task, System.Threading.Tasks.ConfigureAwaitOptions), mdToken: 00000000060065E4) >>> 00007ffc`086ffbc4 e960ffffff jmp 00007ffc`086ffb29从汇编代码中可以观测它是在获取 ExecuteScalarAsync 方法的 Result 结果,有了这个信息就可以翻源代码了,截图如下:
0:000> !tp Using the Portable thread pool. CPU utilization: 11% Workers Total: 602 Workers Running: 602 Workers Idle: 0 Worker Min Limit: 32 Worker Max Limit: 32767然后通过 ~*e !clrstack 观察发现线程都处于 Open() 方法中,输出如下:
OS Thread Id: 0x1a9d4 (23) Child SP IP Call Site 0000007AD4DBE228 00007ff9feb70b24 [HelperMethodFrame_1OBJ: 0000007ad4dbe228] System.Threading.Monitor.ObjWait(Int32, System.Object) 0000007AD4DBE350 00007ff9b655d55e System.Threading.Monitor.Wait(System.Object, Int32) [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Monitor.CoreCLR.cs @ 156] 0000007AD4DBE380 00007ff9b656860e System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ManualResetEventSlim.cs @ 561] 0000007AD4DBE420 00007ff9b6581729 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3072] 0000007AD4DBE4A0 00007ff9b6581516 System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3007] 0000007AD4DBE520 00007ff959e9e9f4 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task, System.Threading.Tasks.ConfigureAwaitOptions) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/TaskAwaiter.cs @ 111] 0000007AD4DBE560 00007ff95752e95b MySql.Data.MySqlClient.MySqlConnection.Open() ...可恶的是 Open() 方法内部也是用 异步转同步 实现的,真的无语了。
退回到低版本的 MySql.Data,继续使用真正的同步版写法。