hangfire内部实验器是同步的,会导致死锁 [复制链接]
发表于 2026-2-1 03:56:50 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
再次碰到dotnet的第三方组件标题,就是hangfire的CoreBackgroundJobPerformer会导致死锁,它是作为hagnfire服务端的job实验器的,它非常的关键,是job可以或许运行的关键,这些库大概读是从很早的dotnetfremework期间移植过来的(我推测的),同样的存在同步调用异步代码的标题,会导致死锁。
它有标题的代码如下:
  1. namespace Hangfire.Server
  2. {
  3.     internal sealed class CoreBackgroundJobPerformer : IBackgroundJobPerformer
  4.     {
  5.         private object InvokeMethod(PerformContext context, object instance, object[] arguments)
  6.             if (context.BackgroundJob.Job == null) return null;
  7.             try
  8.             {
  9.                 var methodInfo = context.BackgroundJob.Job.Method;
  10.                 var method = new BackgroundJobMethod(methodInfo, instance, arguments);
  11.                 var returnType = methodInfo.ReturnType;
  12.                 if (returnType.IsTaskLike(out var getTaskFunc))
  13.                 {
  14.                     if (_taskScheduler != null)
  15.                     {
  16.                         return InvokeOnTaskScheduler(context, method, getTaskFunc);
  17.                     }
  18.                     return InvokeOnTaskPump(context, method, getTaskFunc);
  19.                 }
  20.                 return InvokeSynchronously(method);
  21.             }
  22.             catch (ArgumentException ex)
  23.             {
  24.                 HandleJobPerformanceException(ex, context.CancellationToken, context.BackgroundJob);
  25.                 throw;
  26.             }
  27.             catch (AggregateException ex)
  28.             {
  29.                 HandleJobPerformanceException(ex.InnerException, context.CancellationToken, context.BackgroundJob);
  30.                 throw;
  31.             }
  32.             catch (TargetInvocationException ex)
  33.             {
  34.                 HandleJobPerformanceException(ex.InnerException, context.CancellationToken, context.BackgroundJob);
  35.                 throw;
  36.             }
  37.             catch (Exception ex) when (ex.IsCatchableExceptionType())
  38.             {
  39.                 HandleJobPerformanceException(ex, context.CancellationToken, context.BackgroundJob);
  40.                 throw;
  41.             }
  42.         }
  43.         private object InvokeOnTaskScheduler(PerformContext context, BackgroundJobMethod method, Func<object, Task> getTaskFunc)
  44.         {
  45.             var scheduledTask = Task.Factory.StartNew(
  46.                 InvokeOnTaskSchedulerInternal,
  47.                 method,
  48.                 CancellationToken.None,
  49.                 TaskCreationOptions.None,
  50.                 _taskScheduler);
  51.             var result = scheduledTask.GetAwaiter().GetResult();//同步执行异步
  52.             if (result == null) return null;
  53.             return getTaskFunc(result).GetTaskLikeResult(result, method.ReturnType);
  54.         }
  55.         private static object InvokeOnTaskSchedulerInternal(object state)
  56.         {
  57.             // ExecutionContext is captured automatically when calling the Task.Factory.StartNew
  58.             // method, so we don't need to capture it manually. Please see the comment for
  59.             // synchronous method execution below for details.
  60.             return ((BackgroundJobMethod)state).Invoke();
  61.         }
  62.         private static object InvokeOnTaskPump(PerformContext context, BackgroundJobMethod method, Func<object, Task> getTaskFunc)
  63.         {
  64.             // Using SynchronizationContext here is the best default option, where workers
  65.             // are still running on synchronous dispatchers, and where a single job performer
  66.             // may be used by multiple workers. We can't create a separate TaskScheduler
  67.             // instance of every background job invocation, because TaskScheduler.Id may
  68.             // overflow relatively fast, and can't use single scheduler for multiple performers
  69.             // for better isolation in the default case – non-default external scheduler should
  70.             // be used. It's also great to preserve backward compatibility for those who are
  71.             // using Parallel.For(Each), since we aren't changing the TaskScheduler.Current.
  72.             var oldSyncContext = SynchronizationContext.Current;
  73.             try
  74.             {
  75.                 using (var syncContext = new InlineSynchronizationContext())
  76.                 using (var cancellationEvent = context.CancellationToken.ShutdownToken.GetCancellationEvent())
  77.                 {
  78.                     SynchronizationContext.SetSynchronizationContext(syncContext);
  79.                     var result = InvokeSynchronously(method);
  80.                     if (result == null) return null;
  81.                     var task = getTaskFunc(result);
  82.                     var asyncResult = (IAsyncResult)task;
  83.                     var waitHandles = new[] { syncContext.WaitHandle, asyncResult.AsyncWaitHandle, cancellationEvent.WaitHandle };
  84.                     while (!asyncResult.IsCompleted && WaitHandle.WaitAny(waitHandles) == 0)//这里也同样
  85.                     {
  86.                         var workItem = syncContext.Dequeue();
  87.                         workItem.Item1(workItem.Item2);
  88.                     }
  89.                     return task.GetTaskLikeResult(result, method.ReturnType);
  90.                 }
  91.             }
  92.             finally
  93.             {
  94.                 SynchronizationContext.SetSynchronizationContext(oldSyncContext);
  95.             }
  96.         }
复制代码
有标题的代码就是
var result = scheduledTask.GetAwaiter().GetResult();
以及
while (!asyncResult.IsCompleted && WaitHandle.WaitAny(waitHandles) == 0)
制止现在1.8.22版本照旧没有办理这个标题,有人提了issue了,但是要到2.0.0版本才会办理。

免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金.
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表