1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Hangfire のJob実行時にPerformContextを使用する

Posted at

はじめに

みなさん、Hangfire Serverを使用していて、
Job実体の関数を実行する際に jobId を始めとする、
コンテキストにアクセスしたいと思ったことはありませんか?

(そもそも Hangfire 記事があまり読まれていない! 2020/04/24現在)

今回はそのコンテキストの使用方法を調査しましたので、記事にしたためます。

コンテキスト?

「会話のコンテキスト」って言うように、背景とか文脈みたいな意味ですね。

Hangfireだと PerformContext だったり、 CreatedContext とか
タスクの状態によって様々なコンテキストが定義されています。

PerformContext Class

今回はジョブがなんという JobId で実行されているのか
知りたいケース(例えばJobIdでフォルダを作るなど)で、
それを知るための方法を調査していたらコンテキストに行き着きました。

フォーラムにおける JobIdの取得方法

Use Hangfire job in the code

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(かな?)を使用してそこの依存性を解消していきたいと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?