ASP.NET Core でホステッド サービスを使用するバックグラウンド タスクBackground tasks with hosted services in ASP.NET Core

作成者: Jeow Li HuanBy Jeow Li Huan

ASP.NET Core では、バックグラウンド タスクを ホステッド サービス として実装することができます。In ASP.NET Core, background tasks can be implemented as hosted services. ホストされるサービスは、IHostedService インターフェイスを実装するバックグラウンド タスク ロジックを持つクラスです。A hosted service is a class with background task logic that implements the IHostedService interface. このトピックでは、3 つのホステッド サービスの例について説明します。This topic provides three hosted service examples:

  • タイマーで実行されるバックグラウンド タスク。Background task that runs on a timer.
  • スコープ サービスをアクティブ化するホステッド サービス。Hosted service that activates a scoped service. スコープ サービスは依存関係の挿入 (DI) を使用できます。The scoped service can use dependency injection (DI).
  • 連続して実行される、キューに格納されたバックグラウンド タスク。Queued background tasks that run sequentially.

サンプル コードを表示またはダウンロードします (ダウンロード方法)。View or download sample code (how to download)

ワーカー サービス テンプレートWorker Service template

ASP.NET Core ワーカー サービス テンプレートは、実行時間が長いサービス アプリを作成する場合の出発点として利用できます。The ASP.NET Core Worker Service template provides a starting point for writing long running service apps. ワーカー サービス テンプレートから作成されたアプリで、そのプロジェクト ファイル内のワーカー SDK が指定されます。An app created from the Worker Service template specifies the Worker SDK in its project file:

<Project Sdk="Microsoft.NET.Sdk.Worker">

ホステッド サービス アプリの基礎としてテンプレートを使用するには:To use the template as a basis for a hosted services app:

  1. 新しいプロジェクトを作成します。Create a new project.
  2. [ワーカー サービス] を選択します。Select Worker Service. [次へ] を選択します。Select Next.
  3. [プロジェクト名] フィールドにプロジェクト名を入力するか、既定のプロジェクト名をそのまま使用します。Provide a project name in the Project name field or accept the default project name. 作成 を選択します。Select Create.
  4. [新しい Worker サービスを作成します] ダイアログで、 [作成] を選択します。In the Create a new Worker service dialog, select Create.

PackagePackage

ワーカー サービス テンプレートに基づくアプリは Microsoft.NET.Sdk.Worker SDK を使用し、Microsoft.Extensions.Hosting パッケージへの明示的なパッケージ参照を含んでいます。An app based on the Worker Service template uses the Microsoft.NET.Sdk.Worker SDK and has an explicit package reference to the Microsoft.Extensions.Hosting package. たとえば、サンプル アプリのプロジェクト ファイル (BackgroundTasksSample.csproj) を参照してください。For example, see the sample app's project file (BackgroundTasksSample.csproj).

Microsoft.NET.Sdk.Web SDK を使用する Web アプリの場合、Microsoft.Extensions.Hosting パッケージは共有フレームワークから暗黙的に参照されます。For web apps that use the Microsoft.NET.Sdk.Web SDK, the Microsoft.Extensions.Hosting package is referenced implicitly from the shared framework. アプリのプロジェクト ファイル内の明示的なパッケージ参照は必要ありません。An explicit package reference in the app's project file isn't required.

IHostedService インターフェイスIHostedService interface

IHostedService インターフェイスは、ホストによって管理されるオブジェクトの 2 つのメソッドを定義します。The IHostedService interface defines two methods for objects that are managed by the host:

  • StartAsync(CancellationToken): StartAsync には、バックグラウンド タスクを開始するロジックが含まれています。StartAsync(CancellationToken): StartAsync contains the logic to start the background task. StartAsync は、以下よりも "前に" 呼び出されます。StartAsync is called before:

    既定の動作を変更して、アプリのパイプラインが構成されて ApplicationStarted が呼び出された後で、ホステッド サービスの StartAsync が実行するようにできます。The default behavior can be changed so that the hosted service's StartAsync runs after the app's pipeline has been configured and ApplicationStarted is called. 既定の動作を変更するには、ConfigureWebHostDefaults を呼び出した後でホステッド サービス (次の例では VideosWatcher) を追加します。To change the default behavior, add the hosted service (VideosWatcher in the following example) after calling ConfigureWebHostDefaults:

    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }
    
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                .ConfigureServices(services =>
                {
                    services.AddHostedService<VideosWatcher>();
                });
    }
    
  • StopAsync(CancellationToken):ホストが正常なシャットダウンを実行しているときにトリガーされます。StopAsync(CancellationToken): Triggered when the host is performing a graceful shutdown. StopAsync には、バックグラウンド タスクを終了するロジックが含まれています。StopAsync contains the logic to end the background task. アンマネージ リソースを破棄するには、IDisposableファイナライザー (デストラクター) を実装します。Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    キャンセル トークンには、シャットダウン プロセスが正常に行われないことを示す、既定の 5 秒間のタイムアウトが含まれています。The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. キャンセルがトークンに要求された場合:When cancellation is requested on the token:

    • アプリで実行されている残りのバックグラウンド操作が中止します。Any remaining background operations that the app is performing should be aborted.
    • StopAsync で呼び出されたすべてのメソッドが速やかに戻ります。Any methods called in StopAsync should return promptly.

    ただし、キャンセルが要求された後もタスクは破棄されません—呼び出し元がすべてのタスクの完了を待機します。However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    アプリが予期せずシャットダウンした場合 (たとえば、アプリのプロセスが失敗した場合)、StopAsync は呼び出されないことがあります。If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. そのため、StopAsync で呼び出されたメソッドや行われた操作が実行されない可能性があります。Therefore, any methods called or operations conducted in StopAsync might not occur.

    既定の 5 秒のシャットダウン タイムアウトを延長するには、次を設定します。To extend the default five second shutdown timeout, set:

ホステッド サービスは、アプリの起動時に一度アクティブ化され、アプリのシャットダウン時に正常にシャットダウンされます。The hosted service is activated once at app startup and gracefully shut down at app shutdown. バックグラウンド タスクの実行中にエラーがスローされた場合、StopAsync が呼び出されていなくても Dispose を呼び出す必要があります。If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

BackgroundService 基底クラスBackgroundService base class

BackgroundService は、長期 IHostedService を実装するための基底クラスです。BackgroundService is a base class for implementing a long running IHostedService.

ExecuteAsync(CancellationToken) は、バックグラウンド サービスを実行するために呼び出されます。ExecuteAsync(CancellationToken) is called to run the background service. この実装では、バックグラウンド サービスの有効期間全体を表す Task が返されます。The implementation returns a Task that represents the entire lifetime of the background service. await を呼び出すなどして ExecuteAsync が非同期になるまで、以降のサービスは開始されません。No further services are started until ExecuteAsync becomes asynchronous, such as by calling await. ExecuteAsync では、長時間の初期化作業を実行しないようにしてください。Avoid performing long, blocking initialization work in ExecuteAsync. ホストは StopAsync(CancellationToken) でブロックされ、ExecuteAsync の完了を待機します。The host blocks in StopAsync(CancellationToken) waiting for ExecuteAsync to complete.

IHostedService.StopAsync が呼び出されると、キャンセル トークンがトリガーされます。The cancellation token is triggered when IHostedService.StopAsync is called. サービスを正常にシャットダウンするためのキャンセル トークンが起動すると、ExecuteAsync の実装はすぐに終了します。Your implementation of ExecuteAsync should finish promptly when the cancellation token is fired in order to gracefully shut down the service. それ以外の場合は、シャットダウンのタイムアウト時にサービスが強制的にシャットダウンします。Otherwise, the service ungracefully shuts down at the shutdown timeout. 詳細については、「IHostedService インターフェイス」のセクションを参照してください。For more information, see the IHostedService interface section.

時間が指定されたバックグラウンド タスクTimed background tasks

時間が指定されたバックグラウンド タスクは、System.Threading.Timer クラスを利用します。A timed background task makes use of the System.Threading.Timer class. このタイマーはタスクの DoWork メソッドをトリガーします。The timer triggers the task's DoWork method. タイマーは StopAsync で無効になり、Dispose でサービス コンテナーが破棄されたときに破棄されます。The timer is disabled on StopAsync and disposed when the service container is disposed on Dispose:

public class TimedHostedService : IHostedService, IDisposable
{
    private int executionCount = 0;
    private readonly ILogger<TimedHostedService> _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        var count = Interlocked.Increment(ref executionCount);

        _logger.LogInformation(
            "Timed Hosted Service is working. Count: {Count}", count);
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

前の DoWork の実行が完了するまで Timer は待機されないため、ここで示したアプローチはすべてのシナリオに適しているとは限りません。The Timer doesn't wait for previous executions of DoWork to finish, so the approach shown might not be suitable for every scenario. Interlocked.Increment は、アトミック操作として実行カウンターをインクリメントするために使用されされます。これにより、複数のスレッドによって executionCount が同時に更新されなくなります。Interlocked.Increment is used to increment the execution counter as an atomic operation, which ensures that multiple threads don't update executionCount concurrently.

サービスは、AddHostedService 拡張メソッドを使用して IHostBuilder.ConfigureServices (Program.cs) に登録されます。The service is registered in IHostBuilder.ConfigureServices (Program.cs) with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

バックグラウンド タスクでスコープ サービスを使用するConsuming a scoped service in a background task

BackgroundService 内でスコープ サービスを使用するには、スコープを作成します。To use scoped services within a BackgroundService, create a scope. 既定では、ホステッド サービスのスコープは作成されません。No scope is created for a hosted service by default.

バックグラウンド タスクのスコープ サービスには、バックグラウンド タスクのロジックが含まれています。The scoped background task service contains the background task's logic. 次に例を示します。In the following example:

  • サービスは非同期です。The service is asynchronous. DoWork メソッドは Task を返します。The DoWork method returns a Task. デモンストレーションのために、10 秒の遅延が DoWork メソッドで待機されます。For demonstration purposes, a delay of ten seconds is awaited in the DoWork method.
  • ILogger がサービスに挿入されます。An ILogger is injected into the service.
internal interface IScopedProcessingService
{
    Task DoWork(CancellationToken stoppingToken);
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private int executionCount = 0;
    private readonly ILogger _logger;
    
    public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
    {
        _logger = logger;
    }

    public async Task DoWork(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            executionCount++;

            _logger.LogInformation(
                "Scoped Processing Service is working. Count: {Count}", executionCount);

            await Task.Delay(10000, stoppingToken);
        }
    }
}

ホステッド サービスはスコープを作成してバックグラウンド タスクのスコープ サービスを解決し、DoWork メソッドを呼び出します。The hosted service creates a scope to resolve the scoped background task service to call its DoWork method. ExecuteAsync で待機していた DoWorkTask を返します。DoWork returns a Task, which is awaited in ExecuteAsync:

public class ConsumeScopedServiceHostedService : BackgroundService
{
    private readonly ILogger<ConsumeScopedServiceHostedService> _logger;

    public ConsumeScopedServiceHostedService(IServiceProvider services, 
        ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    public IServiceProvider Services { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service running.");

        await DoWork(stoppingToken);
    }

    private async Task DoWork(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is working.");

        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = 
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWork(stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        await base.StopAsync(stoppingToken);
    }
}

サービスは IHostBuilder.ConfigureServices (Program.cs) に登録されています。The services are registered in IHostBuilder.ConfigureServices (Program.cs). ホステッド サービスは、AddHostedService 拡張メソッドを使用して登録されます。The hosted service is registered with the AddHostedService extension method:

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

キューに格納されたバックグラウンド タスクQueued background tasks

バックグラウンド タスク キューは、.NET 4.x QueueBackgroundWorkItem に基づいています。A background task queue is based on the .NET 4.x QueueBackgroundWorkItem:

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);

    Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = 
        new ConcurrentQueue<Func<CancellationToken, Task>>();
    private SemaphoreSlim _signal = new SemaphoreSlim(0);

    public void QueueBackgroundWorkItem(
        Func<CancellationToken, Task> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        _workItems.Enqueue(workItem);
        _signal.Release();
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out var workItem);

        return workItem;
    }
}

次の QueueHostedService の例では以下のようになります。In the following QueueHostedService example:

  • ExecuteAsync で待機していた BackgroundProcessing メソッドが Taskを返します。The BackgroundProcessing method returns a Task, which is awaited in ExecuteAsync.
  • BackgroundProcessing で、キュー内のバックグラウンド タスクがデキューされ、実行されます。Background tasks in the queue are dequeued and executed in BackgroundProcessing.
  • 作業項目が待機されてから、StopAsync でサービスが停止します。Work items are awaited before the service stops in StopAsync.
public class QueuedHostedService : BackgroundService
{
    private readonly ILogger<QueuedHostedService> _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        ILogger<QueuedHostedService> logger)
    {
        TaskQueue = taskQueue;
        _logger = logger;
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            $"Queued Hosted Service is running.{Environment.NewLine}" +
            $"{Environment.NewLine}Tap W to add a work item to the " +
            $"background queue.{Environment.NewLine}");

        await BackgroundProcessing(stoppingToken);
    }

    private async Task BackgroundProcessing(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = 
                await TaskQueue.DequeueAsync(stoppingToken);

            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    "Error occurred executing {WorkItem}.", nameof(workItem));
            }
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Queued Hosted Service is stopping.");

        await base.StopAsync(stoppingToken);
    }
}

MonitorLoop サービスは、w キーが入力デバイスで選択されると常に、ホステッド サービスのためにタスクのエンキューを処理します。A MonitorLoop service handles enqueuing tasks for the hosted service whenever the w key is selected on an input device:

  • IBackgroundTaskQueueMonitorLoop サービスに挿入されます。The IBackgroundTaskQueue is injected into the MonitorLoop service.
  • IBackgroundTaskQueue.QueueBackgroundWorkItem が呼び出され、作業項目がエンキューされます。IBackgroundTaskQueue.QueueBackgroundWorkItem is called to enqueue a work item.
  • 作業項目により、実行時間の長いバックグラウンド タスクがシミュレートされます。The work item simulates a long-running background task:
    • 5 秒間の遅延が 3 回実行されます (Task.Delay)。Three 5-second delays are executed (Task.Delay).
    • タスクが取り消された場合、try-catch ステートメントによって OperationCanceledException がトラップされます。A try-catch statement traps OperationCanceledException if the task is cancelled.
public class MonitorLoop
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger _logger;
    private readonly CancellationToken _cancellationToken;

    public MonitorLoop(IBackgroundTaskQueue taskQueue, 
        ILogger<MonitorLoop> logger, 
        IHostApplicationLifetime applicationLifetime)
    {
        _taskQueue = taskQueue;
        _logger = logger;
        _cancellationToken = applicationLifetime.ApplicationStopping;
    }

    public void StartMonitorLoop()
    {
        _logger.LogInformation("Monitor Loop is starting.");

        // Run a console user input loop in a background thread
        Task.Run(() => Monitor());
    }

    public void Monitor()
    {
        while (!_cancellationToken.IsCancellationRequested)
        {
            var keyStroke = Console.ReadKey();

            if (keyStroke.Key == ConsoleKey.W)
            {
                // Enqueue a background work item
                _taskQueue.QueueBackgroundWorkItem(async token =>
                {
                    // Simulate three 5-second tasks to complete
                    // for each enqueued work item

                    int delayLoop = 0;
                    var guid = Guid.NewGuid().ToString();

                    _logger.LogInformation(
                        "Queued Background Task {Guid} is starting.", guid);

                    while (!token.IsCancellationRequested && delayLoop < 3)
                    {
                        try
                        {
                            await Task.Delay(TimeSpan.FromSeconds(5), token);
                        }
                        catch (OperationCanceledException)
                        {
                            // Prevent throwing if the Delay is cancelled
                        }

                        delayLoop++;

                        _logger.LogInformation(
                            "Queued Background Task {Guid} is running. " +
                            "{DelayLoop}/3", guid, delayLoop);
                    }

                    if (delayLoop == 3)
                    {
                        _logger.LogInformation(
                            "Queued Background Task {Guid} is complete.", guid);
                    }
                    else
                    {
                        _logger.LogInformation(
                            "Queued Background Task {Guid} was cancelled.", guid);
                    }
                });
            }
        }
    }
}

サービスは IHostBuilder.ConfigureServices (Program.cs) に登録されています。The services are registered in IHostBuilder.ConfigureServices (Program.cs). ホステッド サービスは、AddHostedService 拡張メソッドを使用して登録されます。The hosted service is registered with the AddHostedService extension method:

services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();

MonitorLoopProgram.Main で開始されます。MonitorLoop is started in Program.Main:

var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();

ASP.NET Core では、バックグラウンド タスクを ホステッド サービス として実装することができます。In ASP.NET Core, background tasks can be implemented as hosted services. ホストされるサービスは、IHostedService インターフェイスを実装するバックグラウンド タスク ロジックを持つクラスです。A hosted service is a class with background task logic that implements the IHostedService interface. このトピックでは、3 つのホステッド サービスの例について説明します。This topic provides three hosted service examples:

  • タイマーで実行されるバックグラウンド タスク。Background task that runs on a timer.
  • スコープ サービスをアクティブ化するホステッド サービス。Hosted service that activates a scoped service. スコープ サービスは依存関係の挿入 (DI) を使用できますThe scoped service can use dependency injection (DI)
  • 連続して実行される、キューに格納されたバックグラウンド タスク。Queued background tasks that run sequentially.

サンプル コードを表示またはダウンロードします (ダウンロード方法)。View or download sample code (how to download)

PackagePackage

Microsoft.AspNetCore.App メタパッケージを参照するか、Microsoft.Extensions.Hosting パッケージへのパッケージ参照を追加します。Reference the Microsoft.AspNetCore.App metapackage or add a package reference to the Microsoft.Extensions.Hosting package.

IHostedService インターフェイスIHostedService interface

ホステッド サービスでは、IHostedService インターフェイスを実装します。Hosted services implement the IHostedService interface. このインターフェイスは、ホストによって管理されるオブジェクトの 2 つのメソッドを定義します。The interface defines two methods for objects that are managed by the host:

  • StartAsync(CancellationToken): StartAsync には、バックグラウンド タスクを開始するロジックが含まれています。StartAsync(CancellationToken): StartAsync contains the logic to start the background task. Web ホスト を使用する場合は、サーバーが起動し、IApplicationLifetime.ApplicationStarted がトリガーされた後で、StartAsync が呼び出されます。When using the Web Host, StartAsync is called after the server has started and IApplicationLifetime.ApplicationStarted is triggered. 汎用ホスト を使用する場合は、ApplicationStarted がトリガーされる前に StartAsync が呼び出されます。When using the Generic Host, StartAsync is called before ApplicationStarted is triggered.

  • StopAsync(CancellationToken):ホストが正常なシャットダウンを実行しているときにトリガーされます。StopAsync(CancellationToken): Triggered when the host is performing a graceful shutdown. StopAsync には、バックグラウンド タスクを終了するロジックが含まれています。StopAsync contains the logic to end the background task. アンマネージ リソースを破棄するには、IDisposableファイナライザー (デストラクター) を実装します。Implement IDisposable and finalizers (destructors) to dispose of any unmanaged resources.

    キャンセル トークンには、シャットダウン プロセスが正常に行われないことを示す、既定の 5 秒間のタイムアウトが含まれています。The cancellation token has a default five second timeout to indicate that the shutdown process should no longer be graceful. キャンセルがトークンに要求された場合:When cancellation is requested on the token:

    • アプリで実行されている残りのバックグラウンド操作が中止します。Any remaining background operations that the app is performing should be aborted.
    • StopAsync で呼び出されたすべてのメソッドが速やかに戻ります。Any methods called in StopAsync should return promptly.

    ただし、キャンセルが要求された後もタスクは破棄されません—呼び出し元がすべてのタスクの完了を待機します。However, tasks aren't abandoned after cancellation is requested—the caller awaits all tasks to complete.

    アプリが予期せずシャットダウンした場合 (たとえば、アプリのプロセスが失敗した場合)、StopAsync は呼び出されないことがあります。If the app shuts down unexpectedly (for example, the app's process fails), StopAsync might not be called. そのため、StopAsync で呼び出されたメソッドや行われた操作が実行されない可能性があります。Therefore, any methods called or operations conducted in StopAsync might not occur.

    既定の 5 秒のシャットダウン タイムアウトを延長するには、次を設定します。To extend the default five second shutdown timeout, set:

ホステッド サービスは、アプリの起動時に一度アクティブ化され、アプリのシャットダウン時に正常にシャットダウンされます。The hosted service is activated once at app startup and gracefully shut down at app shutdown. バックグラウンド タスクの実行中にエラーがスローされた場合、StopAsync が呼び出されていなくても Dispose を呼び出す必要があります。If an error is thrown during background task execution, Dispose should be called even if StopAsync isn't called.

時間が指定されたバックグラウンド タスクTimed background tasks

時間が指定されたバックグラウンド タスクは、System.Threading.Timer クラスを利用します。A timed background task makes use of the System.Threading.Timer class. このタイマーはタスクの DoWork メソッドをトリガーします。The timer triggers the task's DoWork method. タイマーは StopAsync で無効になり、Dispose でサービス コンテナーが破棄されたときに破棄されます。The timer is disabled on StopAsync and disposed when the service container is disposed on Dispose:

internal class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Timed Background Service is working.");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

前の DoWork の実行が完了するまで Timer は待機されないため、ここで示したアプローチはすべてのシナリオに適しているとは限りません。The Timer doesn't wait for previous executions of DoWork to finish, so the approach shown might not be suitable for every scenario.

サービスは、AddHostedService 拡張メソッドを使用して Startup.ConfigureServices に登録されます。The service is registered in Startup.ConfigureServices with the AddHostedService extension method:

services.AddHostedService<TimedHostedService>();

バックグラウンド タスクでスコープ サービスを使用するConsuming a scoped service in a background task

IHostedService 内でスコープ サービスを使用するには、スコープを作成します。To use scoped services within an IHostedService, create a scope. 既定では、ホステッド サービスのスコープは作成されません。No scope is created for a hosted service by default.

バックグラウンド タスクのスコープ サービスには、バックグラウンド タスクのロジックが含まれています。The scoped background task service contains the background task's logic. 次の例では、ILogger がサービスに挿入されています。In the following example, an ILogger is injected into the service:

internal interface IScopedProcessingService
{
    void DoWork();
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private readonly ILogger _logger;
    
    public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
    {
        _logger = logger;
    }

    public void DoWork()
    {
        _logger.LogInformation("Scoped Processing Service is working.");
    }
}

ホステッド サービスはスコープを作成してバックグラウンド タスクのスコープ サービスを解決し、DoWork メソッドを呼び出します。The hosted service creates a scope to resolve the scoped background task service to call its DoWork method:

internal class ConsumeScopedServiceHostedService : IHostedService
{
    private readonly ILogger _logger;

    public ConsumeScopedServiceHostedService(IServiceProvider services, 
        ILogger<ConsumeScopedServiceHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }

    public IServiceProvider Services { get; }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is starting.");

        DoWork();

        return Task.CompletedTask;
    }

    private void DoWork()
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is working.");

        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService = 
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            scopedProcessingService.DoWork();
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation(
            "Consume Scoped Service Hosted Service is stopping.");

        return Task.CompletedTask;
    }
}

サービスは Startup.ConfigureServices に登録されています。The services are registered in Startup.ConfigureServices. IHostedService の実装は、AddHostedService 拡張メソッドで登録されます。The IHostedService implementation is registered with the AddHostedService extension method:

services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

キューに格納されたバックグラウンド タスクQueued background tasks

バックグラウンド タスク キューは、.NET Framework 4.x QueueBackgroundWorkItem (暫定的に ASP.NET Core に組み込まれる予定) に基づいています。A background task queue is based on the .NET Framework 4.x QueueBackgroundWorkItem (tentatively scheduled to be built-in for ASP.NET Core):

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);

    Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = 
        new ConcurrentQueue<Func<CancellationToken, Task>>();
    private SemaphoreSlim _signal = new SemaphoreSlim(0);

    public void QueueBackgroundWorkItem(
        Func<CancellationToken, Task> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        _workItems.Enqueue(workItem);
        _signal.Release();
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out var workItem);

        return workItem;
    }
}

QueueHostedService では、キュー内のバックグラウンド タスクは BackgroundService としてデキューされ、実行されます。これは、長時間実行する IHostedService を構成するための基本クラスです。In QueueHostedService, background tasks in the queue are dequeued and executed as a BackgroundService, which is a base class for implementing a long running IHostedService:

public class QueuedHostedService : BackgroundService
{
    private readonly ILogger _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        ILoggerFactory loggerFactory)
    {
        TaskQueue = taskQueue;
        _logger = loggerFactory.CreateLogger<QueuedHostedService>();
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected async override Task ExecuteAsync(
        CancellationToken cancellationToken)
    {
        _logger.LogInformation("Queued Hosted Service is starting.");

        while (!cancellationToken.IsCancellationRequested)
        {
            var workItem = await TaskQueue.DequeueAsync(cancellationToken);

            try
            {
                await workItem(cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                   "Error occurred executing {WorkItem}.", nameof(workItem));
            }
        }

        _logger.LogInformation("Queued Hosted Service is stopping.");
    }
}

サービスは Startup.ConfigureServices に登録されています。The services are registered in Startup.ConfigureServices. IHostedService の実装は、AddHostedService 拡張メソッドで登録されます。The IHostedService implementation is registered with the AddHostedService extension method:

services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();

インデックス ページ モデル クラスで:In the Index page model class:

  • IBackgroundTaskQueue がコンストラクターに挿入され、Queue に割り当てられます。The IBackgroundTaskQueue is injected into the constructor and assigned to Queue.
  • IServiceScopeFactory が挿入され、_serviceScopeFactory に割り当てられます。An IServiceScopeFactory is injected and assigned to _serviceScopeFactory. ファクトリは、スコープ内でサービス作成するための IServiceScope のインスタンス作成に使用されます。The factory is used to create instances of IServiceScope, which is used to create services within a scope. スコープは、アプリの AppDbContext (スコープ サービス) を使用し、データベース レコードを IBackgroundTaskQueue (シングルトン サービス) に書き込むために作成されます。A scope is created in order to use the app's AppDbContext (a scoped service) to write database records in the IBackgroundTaskQueue (a singleton service).
public class IndexModel : PageModel
{
    private readonly AppDbContext _db;
    private readonly ILogger _logger;
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public IndexModel(AppDbContext db, IBackgroundTaskQueue queue, 
        ILogger<IndexModel> logger, IServiceScopeFactory serviceScopeFactory)
    {
        _db = db;
        _logger = logger;
        Queue = queue;
        _serviceScopeFactory = serviceScopeFactory;
    }

    public IBackgroundTaskQueue Queue { get; }

[インデックス] ページで [タスクの追加] ボタンを選択すると、OnPostAddTask メソッドが実行されます。When the Add Task button is selected on the Index page, the OnPostAddTask method is executed. QueueBackgroundWorkItem が呼び出され、作業項目がエンキューされます。QueueBackgroundWorkItem is called to enqueue a work item:

public IActionResult OnPostAddTaskAsync()
{
    Queue.QueueBackgroundWorkItem(async token =>
    {
        var guid = Guid.NewGuid().ToString();

        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var scopedServices = scope.ServiceProvider;
            var db = scopedServices.GetRequiredService<AppDbContext>();

            for (int delayLoop = 1; delayLoop < 4; delayLoop++)
            {
                try
                {
                    db.Messages.Add(
                        new Message() 
                        { 
                            Text = $"Queued Background Task {guid} has " +
                                $"written a step. {delayLoop}/3"
                        });
                    await db.SaveChangesAsync();
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, 
                        "An error occurred writing to the " +
                        "database. Error: {Message}", ex.Message);
                }

                await Task.Delay(TimeSpan.FromSeconds(5), token);
            }
        }

        _logger.LogInformation(
            "Queued Background Task {Guid} is complete. 3/3", guid);
    });

    return RedirectToPage();
}

その他の技術情報Additional resources