@
R4: Worker von WorkerEngine auf IHostedService (Generic Host) - WorkerBase implementiert IHostedService (StartAsync/StopAsync mit CancellationToken, eigener CTS, Guard auf Info.Active); IWorker auf Metadaten + TriggerAsync reduziert - Worker via AddHostedService registriert (Core + Modul); AppHost.Start() startet sie, AppHost.StopAsync() stoppt sie - WorkerEngine auf leichte Registry reduziert (WorkerInfos fuer UI + TriggerWorkerAsync) - LauncherForm startet/stoppt keine Worker mehr (Host-getrieben) - Hosting.Abstractions im Core; Tests angepasst -> 43/43 gruen; smoke-ui + App-Start ok Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> @
This commit is contained in:
+2
-3
@@ -115,7 +115,7 @@ public sealed class LauncherForm : Form
|
|||||||
module.StopAsync(default).GetAwaiter().GetResult();
|
module.StopAsync(default).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
catch { /* Shutdown darf nicht am Modul scheitern */ }
|
catch { /* Shutdown darf nicht am Modul scheitern */ }
|
||||||
_workerEngine.StopAllAsync().GetAwaiter().GetResult();
|
// Worker/Services stoppt der Host in Program.Main via AppHost.StopAsync() nach Application.Run.
|
||||||
|
|
||||||
base.OnFormClosing(e);
|
base.OnFormClosing(e);
|
||||||
}
|
}
|
||||||
@@ -133,14 +133,13 @@ public sealed class LauncherForm : Form
|
|||||||
// Das gesamte Schema (core_ + ct_) läuft über EF-Migrationen, extern via
|
// Das gesamte Schema (core_ + ct_) läuft über EF-Migrationen, extern via
|
||||||
// `dotnet ef database update` angewendet – keine Laufzeit-Migration mehr.
|
// `dotnet ef database update` angewendet – keine Laufzeit-Migration mehr.
|
||||||
|
|
||||||
|
// Worker/Services laufen bereits (Generic Host, AppHost.Start()). Hier nur noch Modul-Start.
|
||||||
foreach (var module in _modules)
|
foreach (var module in _modules)
|
||||||
{
|
{
|
||||||
try { await module.StartAsync(default); }
|
try { await module.StartAsync(default); }
|
||||||
catch (Exception ex) { _logger.Error(module.Name, $"{module.Name}: Start fehlgeschlagen.", ex); }
|
catch (Exception ex) { _logger.Error(module.Name, $"{module.Name}: Start fehlgeschlagen.", ex); }
|
||||||
}
|
}
|
||||||
|
|
||||||
await _workerEngine.StartAllAsync();
|
|
||||||
|
|
||||||
_logger.Info("Core", "IBKRTrader bereit.");
|
_logger.Info("Core", "IBKRTrader bereit.");
|
||||||
SetStatus("Bereit");
|
SetStatus("Bereit");
|
||||||
UpdateButtonStates();
|
UpdateButtonStates();
|
||||||
|
|||||||
+12
-2
@@ -61,10 +61,13 @@ internal static class Program
|
|||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// Zirkuläre Abhängigkeit auflösen: WebApiService braucht die Engine-Referenz.
|
// Zirkuläre Abhängigkeit auflösen: WebApiService braucht die Engine-Referenz (vor dem Start).
|
||||||
AppHost.Services.GetRequiredService<WebApiService>()
|
AppHost.Services.GetRequiredService<WebApiService>()
|
||||||
.SetEngine(AppHost.Services.GetRequiredService<WorkerEngine>());
|
.SetEngine(AppHost.Services.GetRequiredService<WorkerEngine>());
|
||||||
|
|
||||||
|
// Host starten → alle Worker/Services (IHostedService) laufen an.
|
||||||
|
AppHost.Start();
|
||||||
|
|
||||||
// Views registrieren (Core + Module), dann Launcher starten.
|
// Views registrieren (Core + Module), dann Launcher starten.
|
||||||
var uiHost = AppHost.Services.GetRequiredService<ShellUiHost>();
|
var uiHost = AppHost.Services.GetRequiredService<ShellUiHost>();
|
||||||
RegisterCoreViews(uiHost, AppHost.Services);
|
RegisterCoreViews(uiHost, AppHost.Services);
|
||||||
@@ -115,13 +118,20 @@ internal static class Program
|
|||||||
services.AddSingleton<IBKRInstrumentSyncWorker>();
|
services.AddSingleton<IBKRInstrumentSyncWorker>();
|
||||||
services.AddSingleton<IBKRPriceHistoryWorker>();
|
services.AddSingleton<IBKRPriceHistoryWorker>();
|
||||||
|
|
||||||
// Als IWorker registrieren → die WorkerEngine erhält alle über IEnumerable<IWorker>.
|
// Als IWorker registrieren → die WorkerEngine erhält alle über IEnumerable<IWorker> (nur UI/Registry).
|
||||||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<BackupWorker>());
|
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<BackupWorker>());
|
||||||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<WebserverService>());
|
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<WebserverService>());
|
||||||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<WebApiService>());
|
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<WebApiService>());
|
||||||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<IBKRInstrumentSyncWorker>());
|
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<IBKRInstrumentSyncWorker>());
|
||||||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<IBKRPriceHistoryWorker>());
|
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<IBKRPriceHistoryWorker>());
|
||||||
|
|
||||||
|
// Lebenszyklus über den Generic Host (jeder Worker ist ein IHostedService).
|
||||||
|
services.AddHostedService(sp => sp.GetRequiredService<BackupWorker>());
|
||||||
|
services.AddHostedService(sp => sp.GetRequiredService<WebserverService>());
|
||||||
|
services.AddHostedService(sp => sp.GetRequiredService<WebApiService>());
|
||||||
|
services.AddHostedService(sp => sp.GetRequiredService<IBKRInstrumentSyncWorker>());
|
||||||
|
services.AddHostedService(sp => sp.GetRequiredService<IBKRPriceHistoryWorker>());
|
||||||
|
|
||||||
services.AddSingleton<WorkerEngine>();
|
services.AddSingleton<WorkerEngine>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,9 +100,12 @@ Pin `new MariaDbServerVersion(new Version(11, 8, 6))`. Verbindung aus `appsettin
|
|||||||
**R3 abgeschlossen** ✅ – gesamte Persistenz auf EF Core (Migrationen: `InitialCore`, `AddIbkr`, `InitialCongressTrading`), extern via `dotnet ef database update` anzuwenden.
|
**R3 abgeschlossen** ✅ – gesamte Persistenz auf EF Core (Migrationen: `InitialCore`, `AddIbkr`, `InitialCongressTrading`), extern via `dotnet ef database update` anzuwenden.
|
||||||
- Hinweis: nur build-verifizierbar (Unit-Tests ohne DB); Schema-Anwendung extern via `dotnet ef database update` (env `IBKRTRADER_MYSQL`)
|
- Hinweis: nur build-verifizierbar (Unit-Tests ohne DB); Schema-Anwendung extern via `dotnet ef database update` (env `IBKRTRADER_MYSQL`)
|
||||||
|
|
||||||
### R4 – Trading-Kern einфügen
|
### R4 – Worker auf `IHostedService` ✅
|
||||||
- [ ] Risk/Execution/Portfolio/Broker-Seam nach Core/Trading (aus Phase 3 portiert)
|
- [x] `WorkerBase` implementiert `IHostedService`; `IWorker` auf Metadaten+Trigger reduziert
|
||||||
- [ ] Hintergrund-Jobs als `IHostedService`
|
- [x] Worker via `AddHostedService` registriert; Lebenszyklus über `AppHost.Start()`/`StopAsync()`
|
||||||
|
- [x] `WorkerEngine` auf leichte Registry reduziert (WorkerInfos für UI + `TriggerWorkerAsync`)
|
||||||
|
- [x] `LauncherForm` startet/stoppt keine Worker mehr; Build + 43/43 Tests + smoke-ui + App-Start grün
|
||||||
|
- Hinweis: Trading-Kern (Risk/Execution/Portfolio/Broker) wurde bereits in Phase 3 gebaut und in R3 auf EF gehoben
|
||||||
|
|
||||||
### R5 – CongressTrading als vollständige Strategie
|
### R5 – CongressTrading als vollständige Strategie
|
||||||
- [ ] Scraper/Jobs → `IHostedService`; Signal → `ExecutionService`
|
- [ ] Scraper/Jobs → `IHostedService`; Signal → `ExecutionService`
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.4" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" />
|
||||||
<!-- EF Core / Pomelo (MariaDB 11.8.6). EF 8 laeuft auf net10. -->
|
<!-- EF Core / Pomelo (MariaDB 11.8.6). EF 8 laeuft auf net10. -->
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
namespace IBKRTrader.Core.Workers;
|
namespace IBKRTrader.Core.Workers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Vertrag für jeden Worker oder Service im System.
|
/// Metadaten-/Steuer-Vertrag für jeden Worker oder Service. Der Lebenszyklus (Start/Stop) läuft
|
||||||
/// Core und Module müssen dieses Interface implementieren.
|
/// über <see cref="Microsoft.Extensions.Hosting.IHostedService"/> (vom Generic Host getrieben);
|
||||||
|
/// dieses Interface liefert nur Anzeige-Infos und den manuellen Trigger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IWorker
|
public interface IWorker
|
||||||
{
|
{
|
||||||
/// <summary>Anzeigename in dgv_workerlist.</summary>
|
/// <summary>Anzeigename in der Worker-Liste.</summary>
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
|
|
||||||
/// <summary>Modul-Kürzel (z. B. "Core", "CT").</summary>
|
/// <summary>Modul-Kürzel (z. B. "Core", "CT").</summary>
|
||||||
@@ -18,12 +19,6 @@ public interface IWorker
|
|||||||
/// <summary>Live-Daten für die DataGridView-Zeile.</summary>
|
/// <summary>Live-Daten für die DataGridView-Zeile.</summary>
|
||||||
WorkerInfo Info { get; }
|
WorkerInfo Info { get; }
|
||||||
|
|
||||||
/// <summary>Startet den Worker/Service asynchron.</summary>
|
|
||||||
Task StartAsync(CancellationToken ct);
|
|
||||||
|
|
||||||
/// <summary>Stoppt den Worker/Service sauber.</summary>
|
|
||||||
Task StopAsync();
|
|
||||||
|
|
||||||
/// <summary>Löst einen sofortigen, manuellen Run aus (unabhängig vom Zeitplan).</summary>
|
/// <summary>Löst einen sofortigen, manuellen Run aus (unabhängig vom Zeitplan).</summary>
|
||||||
Task TriggerAsync();
|
Task TriggerAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,16 @@ using IBKRTrader.Core.Logging;
|
|||||||
using IBKRTrader.Core.Persistence.Ef;
|
using IBKRTrader.Core.Persistence.Ef;
|
||||||
using IBKRTrader.Core.Persistence.Entities;
|
using IBKRTrader.Core.Persistence.Entities;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
namespace IBKRTrader.Core.Workers;
|
namespace IBKRTrader.Core.Workers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Basisklasse für alle periodischen Worker.
|
/// Basisklasse für alle Worker/Services. Implementiert <see cref="IHostedService"/> – Start/Stop
|
||||||
/// – Verwaltet den internen CancellationToken-Lifecycle
|
/// werden vom Generic Host getrieben. Führt ExecuteAsync() im konfigurierten Interval aus,
|
||||||
/// – Führt ExecuteAsync() im konfigurierten Interval aus
|
/// aktualisiert WorkerInfo und schreibt Runs in core_worker_log.
|
||||||
/// – Aktualisiert WorkerInfo (LastRuntime, NextRuntime, Status, Info)
|
|
||||||
/// – Schreibt Runs in core_worker_log
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class WorkerBase : IWorker
|
public abstract class WorkerBase : IWorker, IHostedService
|
||||||
{
|
{
|
||||||
// ─── Abstrakte Member ─────────────────────────────────────────────────────
|
// ─── Abstrakte Member ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -56,25 +55,37 @@ public abstract class WorkerBase : IWorker
|
|||||||
|
|
||||||
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken externalCt)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// Deaktivierte Worker starten nicht (Info.Active wird von abgeleiteten Klassen/Settings gesetzt).
|
||||||
|
if (!Info.Active)
|
||||||
|
{
|
||||||
|
Info.Status = WorkerStatus.Stopped;
|
||||||
|
Info.Info = "Deaktiviert";
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
if (_runLoop is { IsCompleted: false }) return Task.CompletedTask;
|
if (_runLoop is { IsCompleted: false }) return Task.CompletedTask;
|
||||||
|
|
||||||
// Lazy: RunEvery erst hier setzen, wenn alle Derived-Felder sicher initialisiert sind
|
// Lazy: RunEvery erst hier setzen, wenn alle Derived-Felder sicher initialisiert sind.
|
||||||
if (string.IsNullOrEmpty(Info.RunEvery))
|
if (string.IsNullOrEmpty(Info.RunEvery))
|
||||||
Info.RunEvery = Interval.HasValue ? FormatInterval(Interval.Value) : "Service";
|
Info.RunEvery = Interval.HasValue ? FormatInterval(Interval.Value) : "Service";
|
||||||
|
|
||||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(externalCt);
|
// Eigener CTS (nicht an den Startup-Token des Hosts gebunden – der ist nur für den Startvorgang).
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
_runLoop = Task.Run(() => RunLoopAsync(_cts.Token), _cts.Token);
|
_runLoop = Task.Run(() => RunLoopAsync(_cts.Token), _cts.Token);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StopAsync()
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_cts == null) return;
|
if (_cts == null) return;
|
||||||
await _cts.CancelAsync();
|
await _cts.CancelAsync();
|
||||||
if (_runLoop != null)
|
if (_runLoop != null)
|
||||||
await _runLoop.ConfigureAwait(false);
|
{
|
||||||
|
try { await _runLoop.ConfigureAwait(false); }
|
||||||
|
catch (OperationCanceledException) { /* erwartet */ }
|
||||||
|
}
|
||||||
|
|
||||||
Info.Status = WorkerStatus.Stopped;
|
Info.Status = WorkerStatus.Stopped;
|
||||||
Info.Info = "Gestoppt";
|
Info.Info = "Gestoppt";
|
||||||
|
|||||||
@@ -5,92 +5,31 @@ using IBKRTrader.Core.Logging;
|
|||||||
namespace IBKRTrader.Core.Workers;
|
namespace IBKRTrader.Core.Workers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Orchestriert alle registrierten IWorker.
|
/// Leichtgewichtige Registry über alle Worker/Services. Start/Stop übernimmt der Generic Host
|
||||||
/// – Startet/stoppt Worker
|
/// (jeder Worker ist ein <see cref="Microsoft.Extensions.Hosting.IHostedService"/>). Diese Klasse
|
||||||
/// – Stellt BindingList für DataGridView bereit
|
/// stellt die live-bindbare Liste für die Workers-Ansicht bereit und erlaubt den manuellen Trigger.
|
||||||
/// – Thread-sicher via ConcurrentDictionary
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WorkerEngine
|
public class WorkerEngine
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<IWorker> _workers;
|
|
||||||
private readonly LoggingService _logger;
|
|
||||||
private readonly ConcurrentDictionary<string, IWorker> _registry = new();
|
private readonly ConcurrentDictionary<string, IWorker> _registry = new();
|
||||||
private CancellationTokenSource _cts = new();
|
|
||||||
|
|
||||||
/// <summary>Live-bindbare Liste für dgv_workerlist.</summary>
|
/// <summary>Live-bindbare Liste für die Workers-Ansicht (DataGridView).</summary>
|
||||||
public BindingList<WorkerInfo> WorkerInfos { get; } = [];
|
public BindingList<WorkerInfo> WorkerInfos { get; } = [];
|
||||||
|
|
||||||
public WorkerEngine(IEnumerable<IWorker> workers, LoggingService logger)
|
public WorkerEngine(IEnumerable<IWorker> workers, LoggingService logger)
|
||||||
{
|
{
|
||||||
_workers = workers;
|
foreach (var worker in workers)
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Engine-Lifecycle ─────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public async Task StartAllAsync()
|
|
||||||
{
|
|
||||||
_cts = new CancellationTokenSource();
|
|
||||||
|
|
||||||
foreach (var worker in _workers)
|
|
||||||
{
|
|
||||||
RegisterWorker(worker);
|
|
||||||
|
|
||||||
if (worker.Info.Active)
|
|
||||||
await worker.StartAsync(_cts.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Info("Core", $"WorkerEngine gestartet – {_registry.Count} Worker/Services registriert.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task StopAllAsync()
|
|
||||||
{
|
|
||||||
await _cts.CancelAsync();
|
|
||||||
|
|
||||||
var tasks = _registry.Values.Select(w => w.StopAsync());
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
|
|
||||||
_logger.Info("Core", "WorkerEngine gestoppt.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Einzelsteuerung ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public async Task StartWorkerAsync(string name)
|
|
||||||
{
|
|
||||||
if (!_registry.TryGetValue(name, out var worker)) return;
|
|
||||||
worker.Info.Active = true;
|
|
||||||
await worker.StartAsync(_cts.Token);
|
|
||||||
_logger.Info("Core", $"Worker manuell gestartet: {name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task StopWorkerAsync(string name)
|
|
||||||
{
|
|
||||||
if (!_registry.TryGetValue(name, out var worker)) return;
|
|
||||||
worker.Info.Active = false;
|
|
||||||
await worker.StopAsync();
|
|
||||||
_logger.Info("Core", $"Worker manuell gestoppt: {name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task TriggerWorkerAsync(string name)
|
|
||||||
{
|
|
||||||
if (!_registry.TryGetValue(name, out var worker)) return;
|
|
||||||
await worker.TriggerAsync();
|
|
||||||
_logger.Info("Core", $"Worker manuell ausgelöst: {name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Intern ───────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private void RegisterWorker(IWorker worker)
|
|
||||||
{
|
{
|
||||||
_registry[worker.Name] = worker;
|
_registry[worker.Name] = worker;
|
||||||
|
WorkerInfos.Add(worker.Info);
|
||||||
|
}
|
||||||
|
logger.Info("Core", $"WorkerEngine: {_registry.Count} Worker/Services registriert.");
|
||||||
|
}
|
||||||
|
|
||||||
// WorkerInfo in BindingList eintragen (UI-Thread, falls nötig)
|
/// <summary>Löst einen sofortigen manuellen Run des benannten Workers aus.</summary>
|
||||||
if (WorkerInfos is { } list)
|
public async Task TriggerWorkerAsync(string name)
|
||||||
{
|
{
|
||||||
if (SynchronizationContext.Current != null)
|
if (_registry.TryGetValue(name, out var worker))
|
||||||
list.Add(worker.Info);
|
await worker.TriggerAsync();
|
||||||
else
|
|
||||||
list.Add(worker.Info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,12 @@ public sealed class CongressTradingModule : IModule
|
|||||||
services.AddSingleton<CongressHistoryImportWorker>();
|
services.AddSingleton<CongressHistoryImportWorker>();
|
||||||
services.AddSingleton<CongressScrapeWorker>();
|
services.AddSingleton<CongressScrapeWorker>();
|
||||||
|
|
||||||
// Worker als IWorker registrieren → die WorkerEngine erhält sie über IEnumerable<IWorker>.
|
// Worker als IWorker registrieren (WorkerEngine/UI-Registry) …
|
||||||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<CongressHistoryImportWorker>());
|
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<CongressHistoryImportWorker>());
|
||||||
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<CongressScrapeWorker>());
|
services.AddSingleton<IWorker>(sp => sp.GetRequiredService<CongressScrapeWorker>());
|
||||||
|
// … und als Hosted Services (Lebenszyklus über den Generic Host).
|
||||||
|
services.AddHostedService(sp => sp.GetRequiredService<CongressHistoryImportWorker>());
|
||||||
|
services.AddHostedService(sp => sp.GetRequiredService<CongressScrapeWorker>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterUi(IModuleUiHost host, IServiceProvider services)
|
public void RegisterUi(IModuleUiHost host, IServiceProvider services)
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public class WorkerBaseTests
|
|||||||
|
|
||||||
worker.Runs.Should().Be(1);
|
worker.Runs.Should().Be(1);
|
||||||
|
|
||||||
await worker.StopAsync();
|
await worker.StopAsync(CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -97,7 +97,7 @@ public class WorkerBaseTests
|
|||||||
await worker.TriggerAsync();
|
await worker.TriggerAsync();
|
||||||
await WaitUntilAsync(() => worker.Runs == 2, "Trigger löst zweiten Lauf aus");
|
await WaitUntilAsync(() => worker.Runs == 2, "Trigger löst zweiten Lauf aus");
|
||||||
|
|
||||||
await worker.StopAsync();
|
await worker.StopAsync(CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -112,7 +112,7 @@ public class WorkerBaseTests
|
|||||||
worker.Runs.Should().Be(1);
|
worker.Runs.Should().Be(1);
|
||||||
worker.Info.Info.Should().Contain("boom");
|
worker.Info.Info.Should().Contain("boom");
|
||||||
|
|
||||||
await worker.StopAsync();
|
await worker.StopAsync(CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -122,7 +122,7 @@ public class WorkerBaseTests
|
|||||||
|
|
||||||
await worker.StartAsync(CancellationToken.None);
|
await worker.StartAsync(CancellationToken.None);
|
||||||
await WaitUntilAsync(() => worker.Runs == 1, "erster Lauf erfolgt");
|
await WaitUntilAsync(() => worker.Runs == 1, "erster Lauf erfolgt");
|
||||||
await worker.StopAsync();
|
await worker.StopAsync(CancellationToken.None);
|
||||||
|
|
||||||
worker.Info.Status.Should().Be(WorkerStatus.Stopped);
|
worker.Info.Status.Should().Be(WorkerStatus.Stopped);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user