#はじめに
バックエンドのシステムを利用していると、multipart/form-dataでリクエストを送る/受け付けるということは滅多にないと思います。
しかし、あるプロジェクトで既存システムが何故かマルチパートでXMLを送るように作られてしまっていたために、Web APIでどうしてもmultipart/form-dataを使う必要がありました。
そのとき、非同期メソッドの扱いで罠に嵌った話です。
Web APIでmultipart/form-dataを扱うには
マルチパート自体は下記のように簡単に扱えます。
public void Post(HttpRequestMessage request)
{
if (request.Content.IsMimeMultipartContent())
MultipartMemoryStreamProvider provider = request.Content.ReadAsMultipartAsync().Result;
foreach(var content in provider.Contents)
{
// 各Contentを処理
}
}
}
マルチパートの読み込みで処理がスタックしてしまう
先ほどのソースコードはPostを同期メソッドで作成していました。
この状態で運営をしてみたところ、ReadAsMultipartAsync()で処理がスタックするという障害が発生してしまいました。
色々調べて、行き着いたのがこちらの記事です。
Don't Block on Async Code
One other important point: an ASP.NET request context is not tied to a specific thread (like the UI context is), but it does only allow one thread in at a time.
ということで、
- ASP.NETのRequestコンテキストを使用しているPostメソッド内で、非同期メソッドAを同期的に呼び出す
- 非同期メソッドAの中で、Requestコンテキストに関連する非同期メソッドを呼び出す
という処理が入るとデッドロックが発生するようです。
ReadAsMultipartAsync()も内部でさらに非同期メソッドを呼んでいるのではないかと・・・
非同期メソッドにすれば解決する
POSTメソッド自体を非同期メソッドに変更し、ReadAsMultipartAsync()をロックしないようにすれば、
前述のデッドロックは発生しなくなります。
public async Task PostAsync(HttpRequestMessage request)
{
if (request.Content.IsMimeMultipartContent())
MultipartMemoryStreamProvider provider = await request.Content.ReadAsMultipartAsync();
foreach(var content in provider.Contents)
{
// 各Contentを処理
}
}
}
まとめ
Web APIは極力、非同期処理化を進めましょう。