webentwicklung-frage-antwort-db.com.de

Kurze laufende Hintergrundaufgabe in .NET Core

Ich habe gerade die IHostedService- und die .NET Core 2.1 BackgroundService-Klasse entdeckt. Ich finde die Idee großartig. Dokumentation .

Alle Beispiele, die ich gefunden habe, werden für lange laufende Aufgaben verwendet (bis zum Auftrag), aber ich brauche es nur für kurze Zeit. Welches ist der richtige Weg, dies zu tun?

Zum Beispiel:
Ich möchte einige Abfragen ausführen (diese dauern ca. 10 Sekunden), nachdem die Anwendung gestartet wurde. Und nur im Entwicklungsmodus. Ich möchte den Start der Anwendung nicht verzögern, daher scheint IHostedService ein guter Ansatz zu sein. Ich kann Task.Factory.StartNew nicht verwenden, da ich Abhängigkeitsinjektion brauche.

Momentan mache ich so:

public class UpdateTranslatesBackgroundService: BackgroundService
{
    private readonly MyService _service;

    public UpdateTranslatesBackgroundService(MyService service)
    {
        //MService injects DbContext, IConfiguration, IMemoryCache, ...
        this._service = service;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await ...
    }
}

anlaufen:

public static IServiceProvider Build(IServiceCollection services, ...)
{
    //.....
    if (hostingEnvironment.IsDevelopment())
        services.AddSingleton<IHostedService, UpdateTranslatesBackgroundService>();
    //.....
}

Aber das scheint übertrieben zu sein. Ist es? Registrieren Sie Singleton (dh die Klasse existiert während der Anwendung). Ich brauche das nicht. Erstellen Sie einfach eine Klasse, führen Sie eine Methode aus, und geben Sie eine Klasse an. Alles im Hintergrund.

8
Makla

Es ist nicht nötig, dafür zu zaubern.

Einfach:

  • Registrieren Sie den Dienst, den Sie zur Ausführung benötigen, in ConfigureServices.
  • Lösen Sie die benötigte Instanz in Configure auf und führen Sie sie aus.
  • Um das Blockieren zu vermeiden, verwenden Sie Task.Run.

Sie müssen die Instanz registrieren, oder die Abhängigkeitseingabe funktioniert nicht. Das ist unvermeidlich. Wenn Sie DI brauchen, müssen Sie es tun.

Darüber hinaus ist es trivial, das zu tun, was Sie fragen:

public class Startup
{
  public Startup(IConfiguration configuration)
  {
    Configuration = configuration;
  }

  public IConfiguration Configuration { get; }

  // This method gets called by the runtime. Use this method to add services to the container.
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    services.AddTransient<MyTasks>(); // <--- This
  }

  // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
    if (env.IsDevelopment())
    {
      app.UseDeveloperExceptionPage();

      // Blocking
      app.ApplicationServices.GetRequiredService<MyTasks>().Execute();

      // Non-blocking
      Task.Run(() => { app.ApplicationServices.GetRequiredService<MyTasks>().Execute(); });
    }
    else
    {
      app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseMvc();
  }
}

public class MyTasks
{
  private readonly ILogger _logger;

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

  public void Execute()
  {
    _logger.LogInformation("Hello World");
  }
}

BackgroundService exists speziell für lang laufende Prozesse; Wenn es einmal ist, verwenden Sie es nicht.

2
Doug

Nun, ich denke es gibt mehr als eine Frage hier. Lassen Sie mich zunächst auf etwas hinweisen, das Sie wahrscheinlich von async kennen! = Multithreaded . Daher wird BackgroundService Sie nicht dazu bringen, dass die App "multithreaded" ist. Wenn Sie Blockierungen für diesen Thread durchführen, wird der Startvorgang trotzdem blockiert. Sagen wir in der Klasse, Sie implementieren alle SQL-Abfragen auf eine nicht echte asynchrone Art und Weise, die ähnlich ist 

public class StopStartupService : BackgroundService
    {
        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            System.Threading.Thread.Sleep(1000);
            return Task.CompletedTask;
        }
    }

Dies blockiert weiterhin den Start.

Es gibt also noch eine andere Frage. 

Wie solltest du Hintergrundjobs ausführen?  

In einfachen Fällen Task.Run (Versuchen Sie, Task.Factory.StartNew zu vermeiden, wenn Sie sich nicht sicher sind, wie er konfiguriert werden soll), sollte dies erledigt werden. Dies bedeutet jedoch nicht, dass dies die beste oder eine gute Möglichkeit ist. Es gibt eine Reihe von Open-Source-Bibliotheken, die dies für Sie tun, und es könnte gut sein, einen Blick darauf zu werfen, was sie bieten. Es gibt eine Reihe von Problemen, die Sie möglicherweise nicht kennen, die zu frustrierenden Fehlern führen können, wenn Sie einfach Task.Run.__ verwenden. Die zweite Frage, die ich sehen kann, ist.

Soll ich feuern und in c # vergessen?  

Für mich ist dies ein definitives NEIN (aber XAML-Leute könnten dem nicht zustimmen). Egal was Sie tun, Sie müssen immer nachverfolgen, wann das, was Sie tun, erledigt ist. In Ihrem Fall möchten Sie möglicherweise ein Rollback in der Datenbank durchführen, wenn jemand die App stoppt, bevor die Abfragen abgeschlossen sind. Darüber hinaus möchten Sie wissen, wann Sie die von den Abfragen bereitgestellten Daten verwenden können. BackgroundService hilft Ihnen dabei, die Ausführung zu vereinfachen, ist aber schwer zu verfolgen. 

Solltest du ein Singleton verwenden?  

Wie Sie bereits erwähnt haben, kann die Verwendung von Singletons eine gefährliche Sache sein, insbesondere wenn Sie die Dinge nicht ordnungsgemäß reinigen. Der Kontext des Services, den Sie verwenden, ist jedoch für die gesamte Lebensdauer des Objekts derselbe. Daher hängt alles von Ihrer Implementierung des Dienstes ab, wenn Probleme auftreten.

Ich mache so etwas, um das zu tun, was du willst.

   public interface IStartupJob
    {
        Task ExecuteAsync(CancellationToken stoppingToken);
    }

    public class DBJob : IStartupJob
    {
        public Task ExecuteAsync(CancellationToken stoppingToken)
        {
            return Task.Run(() => System.Threading.Thread.Sleep(10000));
        }
    }

    public class StartupJobService<TJob> : IHostedService, IDisposable where TJob: class,IStartupJob
    {
        //This ensures a single start of the task this is important on a singletone
        private readonly Lazy<Task> _executingTask;

        private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();

        public StartupJobService(Func<TJob> factory)
        {
            //In order for the transient item to be in memory as long as it is needed not to be in memory for the lifetime of the singleton I use a simple factory
            _executingTask = new Lazy<Task>(() => factory().ExecuteAsync(_stoppingCts.Token));
        }

        //You can use this to tell if the job is done
        public virtual Task Done => _executingTask.IsValueCreated ? _executingTask.Value : throw new Exception("BackgroundService not started");

        public virtual Task StartAsync(CancellationToken cancellationToken)
        {

            if (_executingTask.Value.IsCompleted)
            {
                return _executingTask.Value;
            }

            return Task.CompletedTask;
        }

        public virtual async Task StopAsync(CancellationToken cancellationToken)
        {
            if (_executingTask == null)
            {
                return;
            }

            try
            {
                _stoppingCts.Cancel();
            }
            finally
            {
                await Task.WhenAny(_executingTask.Value, Task.Delay(Timeout.Infinite,
                                                              cancellationToken));
            }

        }

        public virtual void Dispose()
        {
            _stoppingCts.Cancel();
        }

        public static void AddService(IServiceCollection services)
        {
            //Helper to register the job
            services.AddTransient<TJob, TJob>();

            services.AddSingleton<Func<TJob>>(cont => 
            {
                return () => cont.GetService<TJob>();
            });

            services.AddSingleton<IHostedService, StartupJobService<TJob>>();
        }
    }
4
Filip Cordas

Es gibt eine Bibliothek namens Communist.StartupTasks , die genau dieses Szenario behandelt. Es ist auf Nuget verfügbar. 

Es wurde speziell für die Ausführung von Aufgaben während des Anwendungsstarts in einer .NET Core App entwickelt. Es unterstützt vollständig die Abhängigkeitsinjektion.

Beachten Sie, dass die Task sequenziell ausgeführt wird und blockiert wird, bis alle Tasks abgeschlossen sind (d. H. Ihre App akzeptiert keine Anforderungen, bis die Starttasks abgeschlossen sind).

1
Brad