这是使用带有监视器和睡眠的异步任务的好习惯吗?

Is this good practice use of an async task with monitor and sleep?

我目前 运行 轮询异步任务(这意味着该异步任务每 1 秒调用一次)。除了我下面的异步任务之外,所有其他项目都需要快速更新。此代码有效,但我想知道这是否是好的做法?

注意:_monitor 只被 DoAsync() 使用

private readonly object _monitor = new object();

private void PolledEverySecond()
{
    _ = DoAsync(); // do this every 5 seconds
 
    // Other stuff
    GetNetworkState();
    GetCurrentVelocity();
    GetCurrentPosition();
    Etc;
}

private async Task DoAsync()
{
    await Task.Run(() =>
    {
        if (!Monitor.IsEntered(_monitor))
        {
            try
            {
                Monitor.Enter(_monitor);
                DoStuff();
            }
            finally
            {
                Thread.Sleep(5000);
                Monitor.Exit(_monitor);
            }
        }            
    });
}

监视器背后的意图。Enter/Monitor。退出和 Thread.Sleep(5000)。 DoAsync() 不是每 1 秒调用一次吗?我有一个非常适合更新内容的轮询服务,我的许多 ViewModel 都在使用它。但是,在 DoAsync() 的情况下,每秒轮询一次就太过分了。因此,通过使其异步并使用监视器,DoStuff() 大约每 5-6 秒被调用一次。

您当前的代码有两个问题,Monitor.IsEnteredMonitor.Enter 调用之间存在竞争条件,ThreadPool 线程与 Thread.Sleep(5000) 阻塞称呼。阻塞 ThreadPool 个线程不是一个好的做法,因为它可能导致池饱和。饱和的 ThreadPool 无法立即响应工作请求,因此程序响应速度变慢。同样在这种情况下,ThreadPool 必须在池中注入更多线程,从而导致内存消耗增加(每个线程至少需要 内存)。

我的建议是从阻塞 Thread.Sleep 切换到异步(非阻塞)Task.Delay 方法,并且还 从线程仿射 Monitor 切换到线程不可知 SemaphoreSlim. In order to check the availability and acquire the semaphore as an atomic operation, you could use the Wait 方法,将零毫秒作为参数传递:

private readonly SemaphoreSlim _semaphore = new(1, 1);

private async Task DoAsync()
{
    await Task.Run(async () =>
    {
        bool acquired = _semaphore.Wait(0);
        if (acquired)
        {
            var delayTask = Task.Delay(5000);
            try
            {
                DoStuff();
            }
            finally
            {
                await delayTask;
                _semaphore.Release();
            }
        }
    });
}

这样 ThreadPool 线程将仅在 DoStuff 执行期间使用,然后将被释放,并可用于其他工作。

在启动 DoStuff 之前创建 Task.Delay(5000) 任务并在之后等待它,具有将 DoStuff 的持续时间包括在 5 秒延迟中的普遍理想效果。如果你不想要这种行为,你可以在同一行中创建和 await 任务:await Task.Delay(5000);