我正在 dotnet core 2.2
中试验 IHostedService
。我的任务是创建 2 个后台长时间运行的任务。
- 第一个是管理
Selenium
浏览器 session (打开/关闭选项卡、解析 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;
}
当实际调用 asyc 方法时,它同步执行,直到第一个真正的异步操作。你真正的异步操作是
await _emailSender.SendEmailAsync(messageToSend);
但只有满足条件才会调用
if (exceptionMessages.Any())
这意味着您的 ExecuteAsync
方法永远不会返回,因此 StartAsync
。
Task.Delay
也是真正的异步方法(Thread.Sleep
不是),因此在点击它之后 StartAsync
将继续并退出,然后你的第二个方法服务将有机会启动。
关于c# - 同时启动多个长时间运行的后台服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56916355/