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

Prometheus Gauge抛出LockRecursionException:是库Bug还是用法错误?

问题:跟踪队列长度时出现LockRecursionException异常

我编写了一段用于跟踪队列长度的代码:

private static Gauge enqueuedWorkGauge = Metrics
        .CreateGauge(...);

public async Task ProcessMessageAsync(Task asyncWork)
{
        // increment counter because something is about to be added to the queue
        enqueuedWorkGauge
            .WithLabels(label1, label2)
            .Inc();

        // enqueue work; enqueueing might need to wait, so it is async
        var workCompletedTask = await queue.EnqueueWorkAsync(asyncWork);

        return workCompletedTask
            .ContinueWith(
                t =>
                {
                    // work is done!
                    // element is not in the queue anymore
                    enqueuedWorkGauge
                        .WithLabels(label1, label2)
                        .Dec();
                },
                default,
                TaskContinuationOptions.ExecuteSynchronously,
                TaskScheduler.Default
            );
}

有时会在日志中观测到如下异常:

System.Threading.LockRecursionException: Recursive read lock acquisitions not allowed in this mode.
         at System.Threading.ReaderWriterLockSlim.TryEnterReadLockCore(TimeoutTracker timeout)
         at Prometheus.Collector`1.TryGetLabelled(LabelSequence instanceLabels, TChild& child)
         at ProcessMessageAsync

该异常抛出自prometheus-net库内部的Collector类相关逻辑,请问这是prometheus-net库的Bug,还是我对任务的使用方式有误?


原因分析与解决方案

这不是prometheus-net的Bug,问题出在你对任务延续的使用方式上:

  • 你指定了*TaskContinuationOptions.ExecuteSynchronously*,这会让延续任务在完成workCompletedTask的线程上同步执行。
  • 当workCompletedTask恰好是在调用await queue.EnqueueWorkAsync的同一个线程上完成时,当前线程可能还持有prometheus-net内部*ReaderWriterLockSlim*的读锁(之前调用Inc()时,*WithLabels*方法会获取读锁),此时再次调用*WithLabels*尝试获取读锁,就会触发递归读锁的异常(因为*ReaderWriterLockSlim*默认不允许递归获取读锁)。

修复方案

推荐使用更简洁安全的await替代ContinueWith,同时用finally确保计数器无论任务成功失败都会递减:

private static Gauge enqueuedWorkGauge = Metrics
        .CreateGauge(...);

public async Task ProcessMessageAsync(Task asyncWork)
{
    enqueuedWorkGauge.WithLabels(label1, label2).Inc();

    var workCompletedTask = await queue.EnqueueWorkAsync(asyncWork);
    
    try
    {
        await workCompletedTask;
    }
    finally
    {
        enqueuedWorkGauge.WithLabels(label1, label2).Dec();
    }
}

如果坚持使用ContinueWith,则需要移除TaskContinuationOptions.ExecuteSynchronously选项,让延续任务在ThreadPool线程上执行,避免递归锁问题:

return workCompletedTask
    .ContinueWith(
        t =>
        {
            enqueuedWorkGauge.WithLabels(label1, label2).Dec();
        },
        default,
        // 移除ExecuteSynchronously选项
        TaskScheduler.Default
    );

这两种方式都能避免递归锁的问题,其中await版本的代码可读性和可维护性更优。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 04:43:37