惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
罗磊的独立博客
雷峰网
雷峰网
量子位
V
Visual Studio Blog
Vercel News
Vercel News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
宝玉的分享
宝玉的分享
月光博客
月光博客
Martin Fowler
Martin Fowler
aimingoo的专栏
aimingoo的专栏
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Microsoft Security Blog
Microsoft Security Blog
博客园 - 叶小钗
腾讯CDC
Engineering at Meta
Engineering at Meta
博客园 - Franky
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
Jina AI
Jina AI
A
About on SuperTechFans

博客园 - Rick Carter

官方APP太难用了,所以我写了个 OpenNas App 死锁是怎么发生的,举个简单的例子 缓存读写代码逻辑的正确姿势 dotnet未捕获异常导致系统崩溃问题 dotnet-dump安装、收集dump和崩溃自动收集dump EFCore中巧妙利用ToQueryString()实现批插(不借助第三方包) 修复达梦EFCore驱动布尔类型兼容问题 dotnet使用redis时需要注意的问题 巧用 using 作用域(IDisposable)的生命周期包装特性 实现前后置处理 非托管内存怎么计算? dotnet集合类型性能优化的两个小儿科的知识点 一个前端树形控件联动勾选框卡顿问题及解决 .net c# Func<Task>及变体做为多播委托异步执行会另开线程的问题 关于EFCore插件API使用中踩过的坑 利用C#9.0中的record提高性能 dotnet CultureInfo遇到欧洲如俄文小数点是逗号想转点的解决办法 hangfire.entityframeworkcore这个库因为System.Threading.Timer未停止也未释放而导致的性能问题 达梦DM.Microsoft.EntityFreameworkCore查询报错invalid cast from DateTime to DateTimeOffset 达梦DOTNET驱动DM.Provider8.3.1.30495存在空字符串插入变DBNull的问题 是否可以考虑做一个dotnet应用的性能诊断工具
hangfire内部执行器是同步的,会导致死锁
Rick Carter · 2026-01-31 · via 博客园 - Rick Carter

再次遇到dotnet的第三方组件问题,就是hangfire的CoreBackgroundJobPerformer会导致死锁,它是作为hagnfire服务端的job执行器的,它非常的关键,是job能够运行的关键,这些库可能读是从很早的dotnetfremework时代移植过来的(我猜测的),同样的存在同步调用异步代码的问题,会导致死锁。

它有问题的代码如下:

namespace Hangfire.Server
{
    internal sealed class CoreBackgroundJobPerformer : IBackgroundJobPerformer
    {
        private object InvokeMethod(PerformContext context, object instance, object[] arguments)
            if (context.BackgroundJob.Job == null) return null;

            try
            {
                var methodInfo = context.BackgroundJob.Job.Method;
                var method = new BackgroundJobMethod(methodInfo, instance, arguments);
                var returnType = methodInfo.ReturnType;

                if (returnType.IsTaskLike(out var getTaskFunc))
                {
                    if (_taskScheduler != null)
                    {
                        return InvokeOnTaskScheduler(context, method, getTaskFunc);
                    }

                    return InvokeOnTaskPump(context, method, getTaskFunc);
                }

                return InvokeSynchronously(method);
            }
            catch (ArgumentException ex)
            {
                HandleJobPerformanceException(ex, context.CancellationToken, context.BackgroundJob);
                throw;
            }
            catch (AggregateException ex)
            {
                HandleJobPerformanceException(ex.InnerException, context.CancellationToken, context.BackgroundJob);
                throw;
            }
            catch (TargetInvocationException ex)
            {
                HandleJobPerformanceException(ex.InnerException, context.CancellationToken, context.BackgroundJob);
                throw;
            }
            catch (Exception ex) when (ex.IsCatchableExceptionType())
            {
                HandleJobPerformanceException(ex, context.CancellationToken, context.BackgroundJob);
                throw;
            }
        }
        private object InvokeOnTaskScheduler(PerformContext context, BackgroundJobMethod method, Func<object, Task> getTaskFunc)
        {
            var scheduledTask = Task.Factory.StartNew(
                InvokeOnTaskSchedulerInternal,
                method,
                CancellationToken.None,
                TaskCreationOptions.None,
                _taskScheduler);

            var result = scheduledTask.GetAwaiter().GetResult();//同步执行异步
            if (result == null) return null;

            return getTaskFunc(result).GetTaskLikeResult(result, method.ReturnType);
        }

        private static object InvokeOnTaskSchedulerInternal(object state)
        {
            // ExecutionContext is captured automatically when calling the Task.Factory.StartNew
            // method, so we don't need to capture it manually. Please see the comment for
            // synchronous method execution below for details.
            return ((BackgroundJobMethod)state).Invoke();
        }

        private static object InvokeOnTaskPump(PerformContext context, BackgroundJobMethod method, Func<object, Task> getTaskFunc)
        {
            // Using SynchronizationContext here is the best default option, where workers
            // are still running on synchronous dispatchers, and where a single job performer
            // may be used by multiple workers. We can't create a separate TaskScheduler
            // instance of every background job invocation, because TaskScheduler.Id may
            // overflow relatively fast, and can't use single scheduler for multiple performers
            // for better isolation in the default case – non-default external scheduler should
            // be used. It's also great to preserve backward compatibility for those who are
            // using Parallel.For(Each), since we aren't changing the TaskScheduler.Current.

            var oldSyncContext = SynchronizationContext.Current;

            try
            {
                using (var syncContext = new InlineSynchronizationContext())
                using (var cancellationEvent = context.CancellationToken.ShutdownToken.GetCancellationEvent())
                {
                    SynchronizationContext.SetSynchronizationContext(syncContext);

                    var result = InvokeSynchronously(method);
                    if (result == null) return null;

                    var task = getTaskFunc(result);
                    var asyncResult = (IAsyncResult)task;

                    var waitHandles = new[] { syncContext.WaitHandle, asyncResult.AsyncWaitHandle, cancellationEvent.WaitHandle };

                    while (!asyncResult.IsCompleted && WaitHandle.WaitAny(waitHandles) == 0)//这里也同样
                    {
                        var workItem = syncContext.Dequeue();
                        workItem.Item1(workItem.Item2);
                    }

                    return task.GetTaskLikeResult(result, method.ReturnType);
                }
            }
            finally
            {
                SynchronizationContext.SetSynchronizationContext(oldSyncContext);
            }
        }

有问题的代码就是
var result = scheduledTask.GetAwaiter().GetResult();
以及
while (!asyncResult.IsCompleted && WaitHandle.WaitAny(waitHandles) == 0)

截至目前1.8.22版本还是没有解决这个问题,有人提了issue了,但是要到2.0.0版本才会解决。