hangfire内部执行器是同步的,会导致死锁
再次遇到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版本才会解决。
作者:Rick Carter
出处:http://pains.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
浙公网安备 33010602011771号