Hangfire使用MongoDB执行长期执行的背景工作,并不断重新启动

时间:2018-07-09 08:32:05

标签: c# asp.net mongodb background-process hangfire

我在使用版本1.6.19 atm和MongoDB作为存储的Hangfire时遇到了问题,我们目前有一种调度方法如下:

BackgroundJob.Schedule(() => DoAsyncTask(parameters, JobCancellationToken.Null), TimeSpan.FromMinutes(X))

任务将运行一个小时以上,并且包含一个循环,以验证作业何时完成。在循环内部,有一个对cancelleToken.ThrowIfCancellationRequested()的调用,以验证是否已请求取消,但是此调用在执行后大约30分钟一直被触发,并在完成之前终止作业。

我一直在搜索有关此问题的信息,但其中大多数与旧版本或InvisibilityTimeout的使用有关,根据this answer,InvisibilityTimeout已被弃用,因此我想知道是否有人遇到过此问题问题和任何可能的解决方案。

谢谢

编辑:经过进一步调查,我发现取消问题只是HangFire在运行30分钟后再次调用该任务的副作用,并且因为我在方法内部设置了验证以避免在重新输入时再次输入该过程仍在运行(以避免重复数据),该过程将被视为已完成,因此被取消。

因此,我面临的真正问题是我无法确定执行大约30分钟后HangFire继续调用该进程的原因,我按照here中所述的步骤将IIS上的应用程序设置为始终运行并防止回收池,但该行为仍然存在。

3 个答案:

答案 0 :(得分:0)

为我的问题实施的解决方案是使用this filter在作业上设置分布式锁定,直到正确完成为止。我对实现进行了小的更改,以包含作业ID并更新对该版本的HangFire使用的新对象的调用,因此将其保留在此处:

public class SkipConcurrentExecutionAttribute : JobFilterAttribute, IServerFilter
{
    private static readonly Logger logger = LogManager.GetCurrentClassLogger();

    private readonly int _timeoutInSeconds;

    public SkipConcurrentExecutionAttribute(int timeoutInSeconds)
    {
        if (timeoutInSeconds < 0) throw new ArgumentException("Timeout argument value should be greater that zero.");

        _timeoutInSeconds = timeoutInSeconds;
    }


    public void OnPerforming(PerformingContext filterContext)
    {
        var resource = $"{filterContext.BackgroundJob.Job.Type.FullName}.{filterContext.BackgroundJob.Job.Method.Name}.{filterContext.BackgroundJob.Id}";

        var timeout = TimeSpan.FromSeconds(_timeoutInSeconds);

        try
        {
            var distributedLock = filterContext.Connection.AcquireDistributedLock(resource, timeout);
            filterContext.Items["DistributedLock"] = distributedLock;
        }
        catch (Exception)
        {
            filterContext.Canceled = true;
            logger.Warn("Cancelling run for {0} job, id: {1} ", resource, filterContext.BackgroundJob.Id);
        }
    }

    public void OnPerformed(PerformedContext filterContext)
    {
        if (!filterContext.Items.ContainsKey("DistributedLock"))
        {
            throw new InvalidOperationException("Can not release a distributed lock: it was not acquired.");
        }

        var distributedLock = (IDisposable)filterContext.Items["DistributedLock"];
        distributedLock.Dispose();
    }
}

所以现在调用后台进程:

[SkipConcurrentExecution(300)]
public async Task DoAsyncTask(parameters, IJobCancellationToken cancellationToken){
    //code execution here
}

我希望这会有所帮助,再次进入的原因仍然未知,所以请随时使用您可能找到的任何信息来扩展此答案。

答案 1 :(得分:0)

ServiceFabric群集中的Hangfire.Core 1.7.6和Hangfire.Mongo 0.5.6遇到相同的问题,我已经使用this guide在我的工作中添加了PerformContext。

这允许获取当前作业的作业ID:var jobId = performContext.BackgroundJob.Id;

计划在30分钟后重新启动的作业具有相同的作业ID。因此可以检查是否没有成功的具有相同ID的作业:

var backgroundJob = performContext.BackgroundJob;
var monitoringApi = JobStorage.Current.GetMonitoringApi();
var succeededCount = (int)monitoringApi.SucceededListCount();
if (succeededCount > 0) 
{
    var queryCount = Math.Min(succeededCount, 1000);

    // read up to 1000 latest succeeded jobs:
    var succeededJobs = monitoringApi.SucceededJobs(succeededCount - queryCount, queryCount);

    // check if job with the same ID already finished:
    if (succeededJobs.Any(succeededKp => backgroundJob.Id == succeededKp.Key)) 
    {
        // The job was already started and succeeded, skip this execution
        return;
    }
}

注意::还必须对job方法进行批注,以使其不会同时启动。超时应有合理的限制,例如6小时:[DisableConcurrentExecution(6 * 60 * 60)]。否则,第二项工作可能会在30分钟后开始,而不是在第一项工作完成后开始。

答案 2 :(得分:-1)

我遇到了同样的问题,并且花了很多时间在Hangfire主题中找到解决方案。但是后来我注意到,取消仅在控制台事件后才触发。

所以问题不在于Hangfire本身,而在于项目 Hangfire.Console 。您是否使用此扩展程序?切换到另一种日志记录方法解决了我所有的问题