はじめに
Web Page や Web API からのファイル取込やまとまったメール送信がそこそこの量になってくると、
こんなに長い時間レスポンスを待つのつらい みたいなケースが出てくるかと思います。
そもそもジョブが終わるまで待たなくていいようなものであれば、
さっさとレスポンスを返して、後はよしなにやっておいて欲しいわけです
そんなときに役立ちそうなバックグラウンドジョブの実行方法を紹介します。
QueueBackgroundWorkItemとHangfire
他にもありますが、この2つについて調べてみました。
QueueBackgroundWorkItem
公式のAPIです。ThreadPool
を使用するTask.Run
で実行するよりはマシですが、
ジョブが完走することは保証されていないので注意が必要です。
CancellationToken
を参照して中断処理を記述できますが、それも完走するとは限りません。
(制限時間を超えたらアウト)
HostingEnvironment.QueueBackgroundWorkItem(cancellationToken =>
{
// something
});
以下は .NET Web Development and Tools Blog からの抜粋です。
概要
- .NET Framework 4.5.2 から利用可能。
- このAPIで登録されたジョブは、ASP.NET Runtimeはそのジョブが実行終了するまでAppDomainのシャットダウンを延期しようとします。
- 通常のThreadPoolで実行されるジョブはこのような動作はしません。
- 実行終了を保証するものではありません。
-
CancellationToken
を参照して途中終了時の処理を記述することができます。
制限
- QBWI APIは、managed AppDomainの外からは呼びだすことはできません。
- AppDomainシャットダウンは90秒(※1)延期できるだけです。
- (※1)HttpRuntimeSection.ShutdownTimeoutとprocessModel shutdownTimeLimitの最小値
- この延期時間を超えたら実行中であってもジョブは強制終了されます。
- バックグラウンドジョブに呼び出し元のExecutionContextは引き継がれません。
-
HttpContext
の情報を使用したければ値をコピーして渡してください。- スレッドセーフではないので
HttpContext
のインスタンス自体を渡さないこと。
- スレッドセーフではないので
-
- アプリケーションプールのシャットダウンが始まったら、スケジューリングされたジョブが実行される保証はありません。
- アプリケーションがシャットダウンされるとき、
CancellationToken
にシグナルが伝えられます。- ジョブはこのTokenを参照し(終了処理などを実行する)努力をするべきです。
- もしジョブがこのTokenを尊重せずに実行し続ければ、ASP.NET Runtimeは、ジョブの終了をまたずにAppDomainをアンロードします。
- 私達はバックグラウンドジョブが完璧に動くということを保証しません。
- 私達はバックグラウンドジョブが不正な処理をしていると判断したらkillするし、
w3wp.exe
プロセスがクラッシュしたらすべてのタスクが死にます。 - 信頼性が必要であればAzureのスケジューリング機能を使うべきです。
- 私達はバックグラウンドジョブが不正な処理をしていると判断したらkillするし、
Hangfire
高機能なOSSのライブラリです。
単純にQueueを入れてバックグラウンドジョブを実行するだけでなく、時間差や定期実行も可能です。
ただ、そんなことより重要なのは、Queue管理がセットになっていることやジョブの実行プロセスが完全に分離できることです。
概要
- .NET Framework 4.5 以上が必要。
-
Client , Job Storage, Sever の3つで構成されている。
- Client
- Queueを登録する
- Job Storage
- ここでQueueデータが管理される
- RedisやSQL Serverが必要
- Server
- ここでバックグラウンドジョブが実行される
-
Webアプリ/コンソールアプリ/Windowsサービスから選択可能。
- ASP.NETから完全に分離できるのでより信頼性の高いジョブ実行が可能になる
- スケーラビリティが高い(負荷に応じてこのサーバーを増減させればいい)
- Client
- ダッシュボードから、ジョブの状態の参照や削除・再実行などができる
試してみた
ASP.NET Web API (Client) + SQL Server (Job Storage) + Console Application (Server) のパターンを試してみました。
- 準備
-
Install-Package Hangfire
2. Client
3. Server - DBファイルを作成
2. app.configとweb.configにConnectionStringを追加 - 初期設定(Config)
- SampleJobを作って、Web APIが呼び出されたときにQueueを登録ようにしておく
-
Config (ver 1.5.3)
// Client
namespace WebApplication1
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
GlobalConfiguration.Configuration.UseSqlServerStorage("DefaultConnection2");
app.UseHangfireDashboard();
}
}
}
// Server
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
GlobalConfiguration.Configuration.UseSqlServerStorage("DefaultConnection2");
using (var server = new BackgroundJobServer())
{
Console.WriteLine("Hangfire Server started. Press any key to exit...");
Console.ReadKey();
}
}
}
}
####SampleJob
namespace ClassLibrary1
{
public class Class1
{
public void SampleJob1(int id)
{
Console.WriteLine("fire and forget!!!!! ");
System.Threading.Thread.Sleep(1000 * id);
}
}
}
####APIが呼び出されたらEnqueue
namespace WebApplication1.Controllers
{
public class TestController : ApiController
{
public void Post(int id)
{
BackgroundJob.Enqueue(() => new ClassLibrary1.Class1().SampleJob1(id));
}
}
}
準備はここまで。
Web API Application(Client) を起動してみます。
するとにDB(Job Storage)にこんなテーブルができます。
ダッシューボードを開いてServersをみると、起動したConsole Appが反映されてますね。
Web APIを実行すると、Console App側でSampleJob1が実行されました!
ジョブの実行が終わると、ダッシュボードのProcessingからSucceededにJobが移動します。
Jobを選ぶとJobのパラメーターや実行時間、処理したサーバープロセスが参照できます。
個人的な感想
- QueueBackgroundWorkItem
- 失敗してもいいジョブ向き?(どんなジョブだ)
- 失敗したときのリトライは?
- 確実に実行したいならQueueを何らかのストレージで管理しないといけない
- 普通にBatch作ったほうがいいのでは感
- 確実に実行したいならQueueを何らかのストレージで管理しないといけない
- Hangfire
- 実行信頼性高い
- ダッシュボードでジョブが可視化されるし、再実行も容易
- スケールできるし、実質ほぼBatchなのでBatch系の処理をすべてこれでやるのも不可能ではなさそう
- Queue管理も委譲するので実装は楽ではあるが依存度は高くなる
おわりに
まだ調べて試してみたというところで、実例はないです。。。
間違いなどありましたら教えていただけると助かります