Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
30
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

Organization

ASP.NETでバックグラウンドジョブを実行してみる

はじめに

Web Page や Web API からのファイル取込やまとまったメール送信がそこそこの量になってくると、
こんなに長い時間レスポンスを待つのつらい:cry: みたいなケースが出てくるかと思います。
そもそもジョブが終わるまで待たなくていいようなものであれば、
さっさとレスポンスを返して、後はよしなにやっておいて欲しいわけです :smile:
そんなときに役立ちそうなバックグラウンドジョブの実行方法を紹介します。

QueueBackgroundWorkItemとHangfire

他にもありますが、この2つについて調べてみました。

:a: 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のスケジューリング機能を使うべきです。

:b: Hangfire

高機能なOSSのライブラリです。
単純にQueueを入れてバックグラウンドジョブを実行するだけでなく、時間差や定期実行も可能です。
ただ、そんなことより重要なのは、Queue管理がセットになっていることやジョブの実行プロセスが完全に分離できることです。

概要


from http://docs.hangfire.io/en/latest/index.html

  • .NET Framework 4.5 以上が必要。
  • Client , Job Storage, Sever の3つで構成されている。
    • Client
      • Queueを登録する
    • Job Storage
      • ここでQueueデータが管理される
      • RedisやSQL Serverが必要
    • Server
  • ダッシュボードから、ジョブの状態の参照や削除・再実行などができる

試してみた

ASP.NET Web API (Client) + SQL Server (Job Storage) + Console Application (Server) のパターンを試してみました。

  • 準備
    1. Install-Package Hangfire
      1. Client
      2. Server
    2. DBファイルを作成
      1. app.configとweb.configにConnectionStringを追加
    3. 初期設定(Config)
    4. 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)にこんなテーブルができます。

Console App(Server) も起動します。

ダッシューボードを開いてServersをみると、起動したConsole Appが反映されてますね。

Web APIを実行すると、Console App側でSampleJob1が実行されました!
console app results

ジョブの実行が終わると、ダッシュボードのProcessingからSucceededにJobが移動します。
succeeded jobs

Jobを選ぶとJobのパラメーターや実行時間、処理したサーバープロセスが参照できます。
details

個人的な感想

  • QueueBackgroundWorkItem
    • 失敗してもいいジョブ向き?(どんなジョブだ)
    • 失敗したときのリトライは?
      • 確実に実行したいならQueueを何らかのストレージで管理しないといけない
        • 普通にBatch作ったほうがいいのでは感
  • Hangfire
    • 実行信頼性高い
    • ダッシュボードでジョブが可視化されるし、再実行も容易
    • スケールできるし、実質ほぼBatchなのでBatch系の処理をすべてこれでやるのも不可能ではなさそう
    • Queue管理も委譲するので実装は楽ではあるが依存度は高くなる

おわりに

まだ調べて試してみたというところで、実例はないです。。。
間違いなどありましたら教えていただけると助かります :pray:

参考資料

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
30
Help us understand the problem. What are the problem?