You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET中如何锁定长耗时任务并向调用者正确反馈运行状态?

问题描述

需求:在处理Web请求的方法中,检测某后台长耗时任务是否正在运行;若未运行,则锁定该任务以阻止后续调用,启动任务并返回对应提示;若任务已运行,则返回任务正在执行的提示。

尝试的两种方案均存在问题:

方案一:跨线程解锁报错

此方案因跨线程调用Monitor.Exit违反.NET锁规则报错:

static object lockMethod = new object();
static object lockTask = new object();

public static string testLocksAndTasks()
{
  //此方法执行速度较快,但为避免其他问题,我对整个方法加锁。这可能与本问题核心需求无关,
  //但由于我确实会在整个请求方法上加锁(因需整合日志及静态变量相关逻辑),故在此保留。
  //该方法整体执行很快,真正耗时的代码在DoWorkInOtherThreadMethod()中,因此需在后台任务中运行。
  lock (lockMethod)
  {
    if (Monitor.TryEnter(lockTask))
    {
      Task.Run(DoWorkInOtherThreadMethod)
        .ContinueWith(ct=>
          Monitor.Exit(lockTask)); //糟糕!在独立任务中解锁不被允许!

      return "We locked lockTask and started the thread!";
    }
    else return "Task called and executed by other request!";
  }
}

方案二:竞态条件+无法反馈真实状态

重构锁的位置后,存在竞态条件,且无法向原调用者反馈真实状态:

public static string testLocksAndTasks()
{
  lock (lockMethod)
  {
    //检查是否已锁定!
    if (!Monitor.IsEntered(lockTask))
    {
      Task.Run(()=>
        {
          //未锁定,尝试获取锁...
          if (Monitor.TryEnter(lockTask))
          {
            DoWorkInOtherThreadMethod();
            Monitor.Exit(lockTask);  //注:我知道此处应包裹在TRY/CATCH/FINALLY中,只是简化示例代码。
          }
          else
          {
            //糟了!这种情况实际可能发生,但我绝不想进入此分支!而且无法向testLocksAndTasks的初始调用者反馈该状态,因为我们处于新线程中!
          }
        });

      return "We locked lockTask and started the thread!";
    }
    else return "Task called and executed by other request!";
  }
}
正确实现方案

核心思路是在请求处理线程中完成锁的获取与状态判断,确保反馈准确;后台任务持有锁直到执行完毕。推荐使用SemaphoreSlim实现,它天然支持跨线程释放,适配场景需求:

// 初始计数1,限制同一时间仅一个任务执行
static readonly SemaphoreSlim taskSemaphore = new SemaphoreSlim(1, 1);
static object lockMethod = new object();

public static string testLocksAndTasks()
{
    lock (lockMethod)
    {
        // 尝试获取信号量,0毫秒超时表示立即返回结果
        if (taskSemaphore.Wait(0))
        {
            // 启动后台任务,用try/finally确保信号量必释放
            _ = Task.Run(async () =>
            {
                try
                {
                    DoWorkInOtherThreadMethod();
                }
                finally
                {
                    taskSemaphore.Release();
                }
            });

            return "任务已启动,正在后台执行!";
        }
        else
        {
            return "任务正在执行中,请稍后再试!";
        }
    }
}

方案说明

  1. SemaphoreSlim适配场景:它允许在获取线程之外释放锁,完美解决Monitor跨线程解锁的限制,无需额外逻辑处理线程归属问题。
  2. 状态判断原子性:在lock(lockMethod)的保护下,taskSemaphore.Wait(0)的结果是原子性的,不会出现竞态条件——要么成功获取锁并启动任务,要么直接返回任务运行中提示,反馈完全准确。
  3. 异常安全:try/finally块确保无论任务成功或抛出异常,信号量都会被释放,避免锁永久占用导致后续请求无法执行任务。

如果坚持使用Monitor,也可以通过静态标志位配合锁实现:

static object lockMethod = new object();
static object taskLock = new object();
static bool isTaskRunning = false;

public static string testLocksAndTasks()
{
    lock (lockMethod)
    {
        if (!isTaskRunning)
        {
            isTaskRunning = true;
            _ = Task.Run(() =>
            {
                lock (taskLock)
                {
                    try
                    {
                        DoWorkInOtherThreadMethod();
                    }
                    finally
                    {
                        // 任务结束后重置状态标志
                        isTaskRunning = false;
                    }
                }
            });

            return "任务已启动,正在后台执行!";
        }
        else
        {
            return "任务正在执行中,请稍后再试!";
        }
    }
}

此方案说明

  • 用isTaskRunning静态标志位记录任务状态,在lock(lockMethod)保护下修改和读取,确保状态判断的原子性。
  • 后台任务中lock(taskLock)作为双重保险,避免极端情况下多个任务同时执行。
  • finally块重置标志位,确保后续请求能正常触发任务。

内容的提问来源于stack exchange,提问作者RLH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 13:40:28