24

.NETコアWeb APIを使用しており、さまざまな強度の要求をデータベースに記録するための軽量なソリューションを探していますが、クライアントに保存プロセスを待たせたくありません。
dnxにはHostingEnvironment.QueueBackgroundWorkItem(..)が実装されておらず、Task.Run(..)は安全ではありません。
優雅なソリューションはありますか?.NETコアのHostingEnvironment.QueueBackgroundWorkItemの代替ソリューション

+5

なぜダウン票ですか?私には非常に良い質問のように見えます。 QueueBackgroundWorkItemは確かに非常に便利です。 –

+2

'HostingEnvironment.QueueBackgroundWorkItem'も安全ではありませんでした。 'Task.Run'よりも安全性は低くなりましたが、安全ではありませんでした。 –

+0

良い質問です。私はsignalR進行報告者(IProgressインタフェースを使用して)を実装しようとしていますが、SignalRの非同期性のために、報告している操作を遅らせることなく、タスクとして(非常に短命ですが)進捗報告を処理する必要があります。 – Shazi

答えて

7

.NET Coreのバックグラウンドジョブには、Hangfire(http://hangfire.io/)を使用できます。例えば

var jobId = BackgroundJob.Enqueue(
    () => Console.WriteLine("Fire-and-forget!")); 
6

QueueBackgroundWorkItemがなくなっていますが、我々は前者によって使用されている代わりにIRegisteredObjectIApplicationLifetimeを、持っています。そして、このようなシナリオではかなり有望です。

アイデア(と私はまだそれはかなり悪いので、確信していないので、注意!)は、を生成し、が新しいタスクを遵守するシングルトンを登録することです。そのシングルトン内では、まだ実行中のタスクを適切に待つために、「停止イベント」を登録することができます。

この「概念」は、ロギング、メール送信などの短時間実行のものに使用できます。ものすごく時間がかかりませんが、現在のリクエストに対して不必要な遅延が発生します。

public class BackgroundPool 
{ 
    protected ILogger<BackgroundPool> Logger { get; } 

    public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime) 
    { 
     if (logger == null) 
      throw new ArgumentNullException(nameof(logger)); 
     if (lifetime == null) 
      throw new ArgumentNullException(nameof(lifetime)); 

     lifetime.ApplicationStopped.Register(() => 
     { 
      lock (currentTasksLock) 
      { 
       Task.WaitAll(currentTasks.ToArray()); 
      } 

      logger.LogInformation(BackgroundEvents.Close, "Background pool closed."); 
     }); 

     Logger = logger; 
    } 

    private readonly object currentTasksLock = new object(); 

    private readonly List<Task> currentTasks = new List<Task>(); 

    public void SendStuff(Stuff whatever) 
    { 
     var task = Task.Run(async() => 
     { 
      Logger.LogInformation(BackgroundEvents.Send, "Sending stuff..."); 

      try 
      { 
       // do THE stuff 

       Logger.LogInformation(BackgroundEvents.SendDone, "Send stuff returns."); 
      } 
      catch (Exception ex) 
      { 
       Logger.LogError(BackgroundEvents.SendFail, ex, "Send stuff failed."); 
      } 
     }); 

     lock (currentTasksLock) 
     { 
      currentTasks.Add(task); 

      currentTasks.RemoveAll(t => t.IsCompleted); 
     } 
    } 
} 

ようなBackgroundPoolはシングルトンとして登録されるべきであるとDIを介して他のコンポーネントによって使用されることができます。私は現在、メールを送信するためにそれを使用しています。

注:背景のタスク内の現在のHttpContextのようなものにアクセスすることはできません。 old solutionは、とにかく禁止するためにUnsafeQueueUserWorkItemを使用します。

あなたはどう思いますか?

更新:ASP.NETコアと

が2.0新しいものは、ASP.NETコア2.1とのより良いの取得のバックグラウンドタスクにあります:Implementing background tasks in .NET Core 2.x webapps or microservices with IHostedService and the BackgroundService class

+0

ApplicationStopped.Registerデリゲートでは、実際には "Task.WaitAll(currentTask.ToArray());"から返されたタスクを待つことはありません。その呼び出しを無意味にします。 – Shazi

+0

WaitAllはすでに待機しています。たぶんあなたはWhen Allを意味しますか? –

+0

はい、あなたは間違いありません。 – Shazi

4

ここであなたをすることができますAxel's answerの微調整バージョンです代理人を渡し、完了したタスクのより積極的なクリーンアップを行います。

using System; 
using System.Collections.Generic; 
using System.Threading.Tasks; 
using Microsoft.AspNetCore.Hosting; 
using Microsoft.Extensions.Logging; 

namespace Example 
{ 
    public class BackgroundPool 
    { 
     private readonly ILogger<BackgroundPool> _logger; 
     private readonly IApplicationLifetime _lifetime; 
     private readonly object _currentTasksLock = new object(); 
     private readonly List<Task> _currentTasks = new List<Task>(); 

     public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime) 
     { 
      if (logger == null) 
       throw new ArgumentNullException(nameof(logger)); 
      if (lifetime == null) 
       throw new ArgumentNullException(nameof(lifetime)); 

      _logger = logger; 
      _lifetime = lifetime; 

      _lifetime.ApplicationStopped.Register(() => 
      { 
       lock (_currentTasksLock) 
       { 
        Task.WaitAll(_currentTasks.ToArray()); 
       } 

       _logger.LogInformation("Background pool closed."); 
      }); 
     } 

     public void QueueBackgroundWork(Action action) 
     { 
#pragma warning disable 1998 
      async Task Wrapper() => action(); 
#pragma warning restore 1998 

      QueueBackgroundWork(Wrapper); 
     } 

     public void QueueBackgroundWork(Func<Task> func) 
     { 
      var task = Task.Run(async() => 
      { 
       _logger.LogTrace("Queuing background work."); 

       try 
       { 
        await func(); 

        _logger.LogTrace("Background work returns."); 
       } 
       catch (Exception ex) 
       { 
        _logger.LogError(ex.HResult, ex, "Background work failed."); 
       } 
      }, _lifetime.ApplicationStopped); 

      lock (_currentTasksLock) 
      { 
       _currentTasks.Add(task); 
      } 

      task.ContinueWith(CleanupOnComplete, _lifetime.ApplicationStopping); 
     } 

     private void CleanupOnComplete(Task oldTask) 
     { 
      lock (_currentTasksLock) 
      { 
       _currentTasks.Remove(oldTask); 
      } 
     } 
    } 
} 
+0

Axelの答えのように、実際には "Task.WaitAll(currentTask.ToArray());"から返されたタスクを待つことはありません。 – Shazi

3

@axelheerはIHostedServiceを述べたように、.NETのコア2.0以上で移動するための方法です。

私はHostingEnvironment.QueueBackgroundWorkItemのようなASP.NET Core Replacementのような軽量が必要でしたので、.NETコアの2.0 IHostedServiceを使用するDalSoft.Hosting.BackgroundQueueを書きました。

PM>インストール・パッケージのごASP.NETコアスタートアップでDalSoft.Hosting.BackgroundQueue

を。CS:

public void ConfigureServices(IServiceCollection services) 
{ 
    services.AddBackgroundQueue(onException:exception => 
    { 

    }); 
} 

ちょうどあなたのコントローラのコンストラクタにBackgroundQueueを追加し、Enqueueを呼び出して、バックグラウンドタスクをキューに。

public EmailController(BackgroundQueue backgroundQueue) 
{ 
    _backgroundQueue = backgroundQueue; 
} 

[HttpPost, Route("/")] 
public IActionResult SendEmail([FromBody]emailRequest) 
{ 
    _backgroundQueue.Enqueue(async cancellationToken => 
    { 
     await _smtp.SendMailAsync(emailRequest.From, emailRequest.To, request.Body); 
    }); 

    return Ok(); 
} 
関連する問題