同时启动多个长 运行 的后台服务
Launching several long running background services at the same time
我正在 dotnet core 2.2
中试验 IHostedService
。我的任务是创建 2 个后台 long-运行 任务。
- 第一个是管理
Selenium
浏览器会话(open/close 选项卡,解析 DOM)并将电子邮件消息放入 ConcurrentBag
内的队列中。
- 第二个后台工作人员每 10 分钟发送一次电子邮件通知,用于
ConcurrentBag
中存在的消息(第一个任务添加)。它还将它们组合在一起,以便只发送 1 条消息。
但是,我同时遇到 运行 2 个托管进程的问题。如果执行,似乎只有第一个托管进程,而第二个进程等待第一个进程完全执行。但是因为我从没想过它会完成 - 第二个过程永远不会开始......
我是在滥用 IHostedService
吗?如果是,那么完成我的任务的最佳架构方法是什么?
这是我目前正在使用的代码(试图完成):
using System;
// ..
namespace WebPageMonitor
{
class Program
{
public static ConcurrentBag<string> Messages = new ConcurrentBag<string>();
static void Main(string[] args)
{
BuildWebHost(args)
.Run();
Console.ReadKey();
}
private static IHost BuildWebHost(string[] args)
{
var hostBuilder = new HostBuilder()
.ConfigureHostConfiguration(config =>
{
config.AddJsonFile("emailSettings.json", optional: true);
config.AddEnvironmentVariables();
})
.ConfigureServices((hostContext, services) =>
{
services.AddOptions();
var bindConfig = new EmailSettings();
hostContext.Configuration.GetSection("EmailSettings").Bind(bindConfig);
services.AddSingleton<EmailSettings>(bindConfig);
services.AddTransient<EmailSender>();
services.AddHostedService<BrowserWorkerHostedService>();
services.AddHostedService<EmailWorkerHostedService>();
});
return hostBuilder.Build();
}
}
}
BrowserWorkerHostedService
public class BrowserWorkerHostedService : BackgroundService
{
private static IWebDriver _driver;
public BrowserWorkerHostedService()
{
InitializeDriver();
}
private void InitializeDriver()
{
try
{
ChromeOptions options = new ChromeOptions();
options.AddArgument("start-maximized");
options.AddArgument("--disable-infobars");
options.AddArgument("no-sandbox");
_driver = new ChromeDriver(options);
}
catch (Exception ex)
{
Program.Messages.Add("Exception: " + ex.ToString());
Console.WriteLine($" Exception:{ex.ToString()}");
throw ex;
}
}
protected override async Task ExecuteAsync(CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
try
{
_driver.Navigate().GoToUrl("https://www.google.com");
Program.Messages.Add("Successfully opened a website!");
// rest of the processing here
Thread.Sleep(60_000);
}
catch (Exception ex)
{
Program.Messages.Add("Exception: " + ex.ToString());
Console.WriteLine(ex.ToString());
Thread.Sleep(120_000);
}
}
_driver?.Quit();
_driver?.Dispose();
}
}
EmailWorkerHostedService
public class EmailWorkerHostedService : BackgroundService
{
private readonly EmailSender _emailSender;
private readonly IHostingEnvironment _env;
public EmailWorkerHostedService(
EmailSender emailSender,
IHostingEnvironment env)
{
_emailSender = emailSender;
_env = env;
}
protected override async Task ExecuteAsync(CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
var builder = new StringBuilder();
List<string> exceptionMessages = new List<string>();
string exceptionMessage;
while (Program.Messages.TryTake(out exceptionMessage))
exceptionMessages.Add(exceptionMessage);
if (exceptionMessages.Any())
{
foreach (var message in exceptionMessages)
{
builder.AppendLine(new string(message.Take(200).ToArray()));
builder.AppendLine();
}
string messageToSend = builder.ToString();
await _emailSender.SendEmailAsync(messageToSend);
}
Thread.Sleep(10000);
}
}
}
编辑: 应用答案中建议的更改后,这是有效代码的当前版本。添加 await
有帮助。
首先,切勿在异步上下文中使用 Thread.Sleep()
,因为它会阻止操作。请改用 Task.Delay()
。我相信这是你的问题。查看 BackgroundService.StartAsync 实现:
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it, this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
当实际调用异步方法时,它会同步执行,直到第一个真正的异步操作。你真正的异步操作是
await _emailSender.SendEmailAsync(messageToSend);
但只有满足条件才会调用
if (exceptionMessages.Any())
这意味着您的 ExecuteAsync
方法永远不会 return,因此 StartAsync
。
Task.Delay
也是真正的异步方法(Thread.Sleep
不是),因此在点击它后 StartAsync
将继续并退出,您的第二个服务将有机会启动。
我正在 dotnet core 2.2
中试验 IHostedService
。我的任务是创建 2 个后台 long-运行 任务。
- 第一个是管理
Selenium
浏览器会话(open/close 选项卡,解析 DOM)并将电子邮件消息放入ConcurrentBag
内的队列中。 - 第二个后台工作人员每 10 分钟发送一次电子邮件通知,用于
ConcurrentBag
中存在的消息(第一个任务添加)。它还将它们组合在一起,以便只发送 1 条消息。
但是,我同时遇到 运行 2 个托管进程的问题。如果执行,似乎只有第一个托管进程,而第二个进程等待第一个进程完全执行。但是因为我从没想过它会完成 - 第二个过程永远不会开始......
我是在滥用 IHostedService
吗?如果是,那么完成我的任务的最佳架构方法是什么?
这是我目前正在使用的代码(试图完成):
using System;
// ..
namespace WebPageMonitor
{
class Program
{
public static ConcurrentBag<string> Messages = new ConcurrentBag<string>();
static void Main(string[] args)
{
BuildWebHost(args)
.Run();
Console.ReadKey();
}
private static IHost BuildWebHost(string[] args)
{
var hostBuilder = new HostBuilder()
.ConfigureHostConfiguration(config =>
{
config.AddJsonFile("emailSettings.json", optional: true);
config.AddEnvironmentVariables();
})
.ConfigureServices((hostContext, services) =>
{
services.AddOptions();
var bindConfig = new EmailSettings();
hostContext.Configuration.GetSection("EmailSettings").Bind(bindConfig);
services.AddSingleton<EmailSettings>(bindConfig);
services.AddTransient<EmailSender>();
services.AddHostedService<BrowserWorkerHostedService>();
services.AddHostedService<EmailWorkerHostedService>();
});
return hostBuilder.Build();
}
}
}
BrowserWorkerHostedService
public class BrowserWorkerHostedService : BackgroundService
{
private static IWebDriver _driver;
public BrowserWorkerHostedService()
{
InitializeDriver();
}
private void InitializeDriver()
{
try
{
ChromeOptions options = new ChromeOptions();
options.AddArgument("start-maximized");
options.AddArgument("--disable-infobars");
options.AddArgument("no-sandbox");
_driver = new ChromeDriver(options);
}
catch (Exception ex)
{
Program.Messages.Add("Exception: " + ex.ToString());
Console.WriteLine($" Exception:{ex.ToString()}");
throw ex;
}
}
protected override async Task ExecuteAsync(CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
try
{
_driver.Navigate().GoToUrl("https://www.google.com");
Program.Messages.Add("Successfully opened a website!");
// rest of the processing here
Thread.Sleep(60_000);
}
catch (Exception ex)
{
Program.Messages.Add("Exception: " + ex.ToString());
Console.WriteLine(ex.ToString());
Thread.Sleep(120_000);
}
}
_driver?.Quit();
_driver?.Dispose();
}
}
EmailWorkerHostedService
public class EmailWorkerHostedService : BackgroundService
{
private readonly EmailSender _emailSender;
private readonly IHostingEnvironment _env;
public EmailWorkerHostedService(
EmailSender emailSender,
IHostingEnvironment env)
{
_emailSender = emailSender;
_env = env;
}
protected override async Task ExecuteAsync(CancellationToken stopToken)
{
while (!stopToken.IsCancellationRequested)
{
var builder = new StringBuilder();
List<string> exceptionMessages = new List<string>();
string exceptionMessage;
while (Program.Messages.TryTake(out exceptionMessage))
exceptionMessages.Add(exceptionMessage);
if (exceptionMessages.Any())
{
foreach (var message in exceptionMessages)
{
builder.AppendLine(new string(message.Take(200).ToArray()));
builder.AppendLine();
}
string messageToSend = builder.ToString();
await _emailSender.SendEmailAsync(messageToSend);
}
Thread.Sleep(10000);
}
}
}
编辑: 应用答案中建议的更改后,这是有效代码的当前版本。添加 await
有帮助。
首先,切勿在异步上下文中使用 Thread.Sleep()
,因为它会阻止操作。请改用 Task.Delay()
。我相信这是你的问题。查看 BackgroundService.StartAsync 实现:
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it, this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
当实际调用异步方法时,它会同步执行,直到第一个真正的异步操作。你真正的异步操作是
await _emailSender.SendEmailAsync(messageToSend);
但只有满足条件才会调用
if (exceptionMessages.Any())
这意味着您的 ExecuteAsync
方法永远不会 return,因此 StartAsync
。
Task.Delay
也是真正的异步方法(Thread.Sleep
不是),因此在点击它后 StartAsync
将继续并退出,您的第二个服务将有机会启动。