命名信号量无法在 Azure App Service 上托管的 ASP .Net Core 5 Web API 上工作

Named semaphore not working on ASP .Net Core 5 Web API hosted on Azure App Service

我在 Azure App Service 上有一个 ASP .Net Core 5 Web API 运行ning。 每天一次,它 运行 是一个托管后台服务。 因为我只希望此服务每天 运行 一次(凌晨 2 点),并且因为我的应用服务已打开自动扩展(当 CPU 或内存 > 70% 时,将实例增加 1,最多 3 个实例),我实现了一个命名信号量,以防止多个实例同时 运行ning。

这是托管后台服务的代码。我对它进行了一些简化以显示要点。

public class MyBackgroundService : BackgroundService
{
    private Timer _timer;
    private readonly ILogger<MyBackgroundService> _logger;

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

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        TimeSpan timeTillNextRun = CalculateTimeTillNextRun();
        _timer = new Timer(DoWork, null, timeTillNextRun, new TimeSpan(1, 0, 0, 0));
    }

    private async void DoWork(object state)
    {
        try
        {
            _timer.Change(Timeout.Infinite, Timeout.Infinite);
            await RunAsync();
        }

        finally
        {
            TimeSpan timeTillNextRun = CalculateTimeTillNextRun();
            _timer.Change(timeTillNextRun, new TimeSpan(1, 0, 0, 0));
        }
    }

    private async Task RunAsync()
    {
        using (Semaphore semaphore = new Semaphore(1, 1, "MyBackgroundService"))
        {
            if (!semaphore.WaitOne(1))
            {
                return;
            }

            try
            {
                _logger.LogInformation(null, "Started.");                    
                // Do stuff
            }

            finally
            {
                semaphore.Release();
            }
        }
    }
}

所以我有一个每天凌晨 2 点触发的计时器。当计时器触发时,我暂停它,然后调用 RunAsync()。在 RunAsunc() 中,我设置了一个命名的(系统)信号量。我只等待 1 毫秒,因为如果信号量正在使用中,我什至不想等待它完成,因为这个后台服务应该 运行 每天一次。

但是,当我检查日志时,我看到它同时 运行 多达 6 次(好吧,在大约 3 秒内),每个服务两次。

id, machine_name, status, time_stamp
'9577', 'RD2818786D33F4', 'Started.', '2021-08-16 02:00:06'
'9578', 'RD2818786D33F4', 'Started.', '2021-08-16 02:00:08'
'9579', 'RD2818786D0367', 'Started.', '2021-08-16 02:00:08'
'9580', 'RD2818786D1D19', 'Started.', '2021-08-16 02:00:09'
'9581', 'RD2818786D0367', 'Started.', '2021-08-16 02:00:09'
'9582', 'RD2818786D1D19', 'Started.', '2021-08-16 02:00:10'

正如您在上面看到的,它首先在 01:00:06 运行,然后 2 秒后在同一台机器上再次出现,然后立即在另一台机器上再次出现,然后一秒钟后又出现两次,最后又过了一​​秒。

现在,查看“machine_name”列,应用服务的每个实例似乎 运行 在不同的 VM 上运行。我想,因为所有实例都在同一个服务计划中,所以它应该是同一个虚拟机,但我想我错了。所以,我想这就是信号量不工作的原因——它不能在不同的机器上工作。 st运行ge 的问题是它似乎也不在同一台机器上工作。正如您在上面的日志中看到的,每个实例上的 RunAsync() 运行 两次。现在它并不总是那样。例如,这是前一天的日志:

id, machine_name, status, time_stamp
'9569', 'RD2818786D2D5E', 'Started.', '2021-08-15 02:00:05'
'9570', 'RD2818786D2D5E', 'Started.', '2021-08-15 02:00:06'

这里只有 运行 两次,一次。同样,它应该只有 运行 一次 - 特别是看到它在同一台机器上。

我是不是用错了Semaphore?我知道它可能无法跨 VM 使用,但即使在同一台 VM 上似乎也无法使用。

如有任何建议,我们将不胜感激。谢谢。

P.s。我知道最好从 API 中删除此托管服务并将其粘贴到 Azure Function 或 Azure WebJob 中。事实上,我会尽快这样做。但我仍然很想知道为什么我的信号量不工作。

Semaphore 似乎每次都在创建一个新的信号量。我不知道为什么。这发生在不同的进程上,甚至发生在单个进程上。按照 Steeeve 的建议,我在信号量名称中添加了“Global//”前缀,但没有任何区别。我什至在信号量构造函数中记录了第四个参数(布尔参数)的值,再次按照 Steeeve 的建议,看看它是否真的每次都在创建一个新的信号量,它真的是。我尝试了多种方法,甚至从 Using() 块中删除了信号量(因为我认为这可能以不应该的方式破坏了信号量)但没有任何区别。

最终我像最初一样回到了 SemaphoreSlim。这并没有给我创建 named/system 信号量的选项,就像 Semaphore 所做的那样,但至少它适用于单个进程。

似乎信号量根本无法在托管在 Azure 应用服务上的应用程序上运行,至少在像我这样设置为自动横向扩展的应用程序上是这样。我可以理解这一点,因为看起来 Azure 在扩展时会为应用程序的每个实例创建一个新的 VM(至少有时 - 我不能保证总是这样)但奇怪的是它似乎没有甚至在同一个虚拟机上工作。根据我的理解,named/system 信号量在 OS 级别工作 - 即它应该在机器内跨进程工作。

谢谢大家的帮助。