Code

2015年5月31日日曜日

Task.Run と Autofac データベース Connection 問題から、サーバー側マルチスレッドの検討

 今開発しているシステムは HTTP を使って、RESTful API です。Front end は iOS から、Android、WebSite 全部サポートしています。デバイスの 3G 回線でも、できるだけ速くロードするために、いろいろ工夫しました。例えば、一つ目のレスポンスに各リソースをリクエストする URL を入れて、実際の作成は後回しして、マルチスレッドを使います。さらに、Task.Run を使って、できるだけ時間のかかる処理を別のスレッドで行うようにします。一方、Oracle DB Connection などは Autofac を使って、 DI の .InstancePerRequest() で各スレッドに配っています。そうすると、Task.Run は Thread Pool に新しいスレッドを申請してので、そのスレッドでは前の Request のスレッドのリソースが使えなくなりました。。。"Invalid operation on a closed object" みたいなエラーが発生したりしています。毎回ではないですが、かなりの確率で発生しています。
 解決の方法としては、Task.Run を使ってるスレッドの中に、自分で Autofac の LifetimeScope コントロールすることです。 まず、LifetimeScope の Provider を作って、RequestLifetimeScopeTag を ILifetimeScope の作成関数に設定することで、この ILifetimeScope は Request Scope として指定できます。ただ、自分で ILifetimeScope を作ったから、Scope の Dispose() 関数を自分で呼び出す必要になります。後日まだどうするかのコードを貼り付けます。
 ここでまず話したいことは Task.Run を使う場合、スレッドの切り替え処理もありますので、一般的に 50 ms 以下のタスクでは別スレッドを使う必要がないのです。
 Task.Run は Back End でファイルを書く場合や、HttpClient を使って、他のスステムに Http Request を発行する場合使ったほうがいいです。一般的な DB アクセスには速度がかなり早いので、20ms ぐらいと言われているので、必要がないと思われています。
 もし Loop を使って、DB 更新を行う場合や、複数のテーブルを更新するが更新結果は Front end に返すする必要がない場合、できるだけ小回りにして、複数回で更新したほうがいいです。この処理は Task.Run を使うより、Hangfire などの Background タスク Runner を使ったほうがいいです。Hangfire は DB を使っているので、Retry 処理もあるし、Task.Run より安全です。
 Front end から見ると、Http API を呼ぶとき、レスポンスが戻ってきたら、 Callback 関数が呼ばれるので、一回の Http Request より、複数の Request を発行して、各 Callback 関数を呼び出して、UI をアップデートしたほうが速いです。Http Request 自体がマルチスレッドでサーバー側で処理するので、それを最大限に使わないと。

 それでは。

0 件のコメント:

コメントを投稿