TAPベースに非同期処理に関連して、
ネットを調べると、「同期と非同期の混在によるデッドロック」みたいな記事が多い。
これ、よく調べてみると、.NET FRAMEWORKの話だったりして、
.NET(core以降)のASPでは、どうやら事情が違うようです。
(※GUIについては、ここでは触れません)
CUI(コンソール)アプリケーションについては、もともとSyncronizedContextがないから、
デッドロックしないよ、みたいな話はあるのですが、
ASP.NET Core以降は、SyncronizedContextがなくなったため、よくあるデッドロックというのは起きません。
(参考)
https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html
https://qiita.com/jun1s/items/c178d37a2cbcd8594461
というわけで、デッドロックしそうなコードをかき集めて検証してみました。
下記コードの検証環境は以下の通りです。
$ dotnet --version
8.0.204
$ cat /etc/os-release
NAME="Pop!_OS"
VERSION="22.04 LTS"
(パターン1)
app.MapGet("/test1", async () =>
{
Console.WriteLine("test 1-------------------------");
Console.WriteLine($"before - {Thread.CurrentThread.ManagedThreadId}"); // 1: スレッドIDは15
var ret = MyClass.Get().Result;
Console.WriteLine($"after - {Thread.CurrentThread.ManagedThreadId}"); // 4: スレッドIDは15
return ret;
});
class MyClass
{
public static async Task<string> Get()
{
Console.WriteLine($"before in - {Thread.CurrentThread.ManagedThreadId}"); // 2: スレッドIDは15
var ret = await Task.Run(() => "ok");
Console.WriteLine($"after in - {Thread.CurrentThread.ManagedThreadId}"); // 3: スレッドIDは14 ★ここでスレッドが変わっている
return ret;
}
}
(パターン2)
app.MapGet("/test4", async () =>
{
Console.WriteLine("test 4-------------------------");
Console.WriteLine($"before - {Thread.CurrentThread.ManagedThreadId}"); // 1: スレッドIDは9
var result = MyClass.GetFromHttp().Result;
Console.WriteLine($"after - {Thread.CurrentThread.ManagedThreadId}"); // 4: スレッドIDは9
return result;
});
class MyClass
{
public static async Task<string> GetFromHttp()
{
var client = new HttpClient();
Console.WriteLine($"before in - {Thread.CurrentThread.ManagedThreadId}"); // 2: スレッドIDは9
var response = await client.GetAsync("http://localhost:8080");
Console.WriteLine($"after in - {Thread.CurrentThread.ManagedThreadId}"); // 3: スレッドIDは7 ★ここでスレッドが変わっている
return response.Content.ReadAsStringAsync().Result;
}
}
というわけで、色々勘違いしそうなんですが、
ASP.NET Core以降では、「同期と非同期の混在」によるデッドロックは起きないようです。
つまり、「ConfigureAwait(false)」がなくても良いのです。
※すいません、間違ってたら教えて下さい。
とはいえ、ライブラリアプリケーションの場合は、
どの実行基盤で使われるかわからないので、「ConfigureAwait(false)」はつけておいたほうが良いかと思います。
C#のasync/awaitは沼だなといつも思う。
わからないことだらけです。
ちょっとしか見てないんですが、Restsharp なんかでは内部でSyncronizedContextを持っているようで、
どうしてこうなっているのかは謎が深まるばかりです。
いつか、スッキリ整理してまとめたいと思うこの頃。
できれば、IO待ちに関しては、「AWAIT」ではなく「NOWAIT」にして欲しかったりする。
つまり、非同期をデフォルトってことですかね。(新しいMSの言語まで待ちかな?)
以上
コメントがあればどうぞ