はじめに
みなさん、Hangfire Serverを使用していて、
Job実体の関数を実行する際に jobId を始めとする、
コンテキストにアクセスしたいと思ったことはありませんか?
(そもそも Hangfire
記事があまり読まれていない! 2020/04/24現在)
今回はそのコンテキストの使用方法を調査しましたので、記事にしたためます。
コンテキスト?
「会話のコンテキスト」って言うように、背景とか文脈みたいな意味ですね。
Hangfireだと PerformContext
だったり、 CreatedContext
とか
タスクの状態によって様々なコンテキストが定義されています。
今回はジョブがなんという JobId
で実行されているのか
知りたいケース(例えばJobIdでフォルダを作るなど)で、
それを知るための方法を調査していたらコンテキストに行き着きました。
フォーラムにおける JobIdの取得方法
For achieving your goal, just add PerformContext to your job method:
public void SendEmail(string name, PerformContext context) { string jobId = context.BackgroundJob.Id; }
And pass null for it when enqueuing a job:
BackgroundJob.Enqueue(() => SendEmail(name, null));
とありますね。
実行関数にPerformContext型の引数を追加して、キューするときにnullを渡せばいいいと。
コードの参照
実際にソースコードを見てみます。
var parameters = context.BackgroundJob.Job.Method.GetParameters();
var result = new List<object>(context.BackgroundJob.Job.Args.Count);
for (var i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
var argument = context.BackgroundJob.Job.Args[i];
var value = Substitutions.ContainsKey(parameter.ParameterType)
? Substitutions[parameter.ParameterType](context)
: argument;
result.Add(value);
}
確かに、parameters
の型をチェックして、
一致するもののargument(キュー時に渡された引数)とSubstitutionsの関数で置き換えています。
問題のSubstitutionsは何かというと
internal static readonly Dictionary<Type, Func<PerformContext, object>> Substitutions
= new Dictionary<Type, Func<PerformContext, object>>
{
{ typeof (IJobCancellationToken), x => x.CancellationToken },
{ typeof (CancellationToken), x => x.CancellationToken.ShutdownToken },
{ typeof (PerformContext), x => x }
};
このように定義されています。
つまり、IJobCancellationToken
, CancellationToken
, PerformContext
型の引数はすべて
context
に基づく値に置換されるわけです。
ここでいう context
とは PerformContext
型であり、
PerformContext
型の引数は context
そのものが渡されるわけですね( x => x
より)
実装
今回の実装したコードは以下のとおりです。
これを実行すると、Super heavy task
の後に JobId が出力されるはずです。
…略…
Super heavy task #5ea2bd72f7e04553001e545c
DummyAwesomeService#DoSomething start
Super heavy task #5ea2bd72f7e04553001e5456
DummyAwesomeService#DoSomething start
DummyAwesomeService#DoSomething end
Done: task #9
DummyAwesomeService#DoSomething end
Done: task #8
2020-04-24 07:20:49 [INFO] (server.Filters.ReportPerformanceFilter) {"latency":11650,"duration":3010}
2020-04-24 07:20:49 [INFO] (server.Filters.ReportPerformanceFilter) {"latency":1165
7,"duration":3014}
JobIdが変わるタイミング
public void Execute(string strText, PerformContext context) {
Console.WriteLine($"Super heavy task #{strText} #{context.BackgroundJob.Id}");
awesomeService.DoSomething();
throw new Exception("something wrong!");
Console.WriteLine($"Done: {strText}");
}
このように、一旦タスクの途中で例外を吐いて、故意に止めてみます。
するとHangfireで自動的に再実行されるのですが…
Super heavy task #task #0 #5ea2bdea9f055b342c65f067
…中略…
Super heavy task #task #0 #5ea2bdea9f055b342c65f067
と、JobId
は変わっていないことがわかります。
JobId
は Jobが生成されたタイミングで付与されるようです。
終わりに
今回はタスクに対して、実行時のコンテキストを付与する方法を書きました。
しかし今回の書き方では、
タスククラスに対して Hangfire への依存をもたせてしまいました。
これでは純粋に機能だけでテストしたい場合に困ります。
次回以降、DependencyInjection(かな?)を使用してそこの依存性を解消していきたいと思います。