c#-4.0 - 针对特定异常的 Hangfire 自动重试

标签 c#-4.0 hangfire

我正在使用hangfire进行作业调度和自动重试作业,我启用了10次。此作业处理特定文件夹位置中的文件。在某些情况下,共享位置中不会存在文件。我想要的是,如果 filenofoundexception。我不想在hangfire 中重试该作业,并且该作业应该会失败。但其他异常作业应该重试10次。

最佳答案

我个人会创建自己的方法属性来解决这个问题。

我已经根据原始的hangfire attribute 为您编写了一些代码。在 github 上。

实现示例:

[FileNotFoundExceptionNoRetry(Attempts = 10)]//Retry 10 times
    public void ProcessFilesJob()
    {
        try
        {
            //Process files in shared location... 
        }
        catch (Exception)
        {
            throw;
        }
    }

理论上,如果抛出 FileNotFoundException 异常,则以下属性代码应使作业处于“失败”状态,并继续重试任何其他异常。

我的更改是在OnStateElection方法下进行的。 请记住这是未经测试的代码。

public sealed class FileNotFoundExceptionNoRetryAttribute : JobFilterAttribute, IElectStateFilter, IApplyStateFilter
{
    private static readonly ILog Logger = LogProvider.GetCurrentClassLogger();
    private const int DefaultRetryAttempts = 10;

    private int _attempts;

    public FileNotFoundExceptionNoRetryAttribute()
    {
        Attempts = DefaultRetryAttempts;
        LogEvents = true;
        OnAttemptsExceeded = AttemptsExceededAction.Fail;
    }

    public int Attempts
    {
        get { return _attempts; }
        set
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException("value", "Attempts value must be equal or greater than zero.");
            }
            _attempts = value;
        }
    }

    public AttemptsExceededAction OnAttemptsExceeded { get; set; }

    public bool LogEvents { get; set; }

    public void OnStateElection(ElectStateContext context)
    {
        var failedState = context.CandidateState as FailedState;
        if (failedState != null && failedState.Exception != null && failedState.Exception is FileNotFoundException)
        {//FileNotFoundException was thrown dont retry.

            Attempts = 0;
            OnAttemptsExceeded = AttemptsExceededAction.Fail;
        }

        if (failedState == null)
        {
            // This filter accepts only failed job state.
            return;
        }

        var retryAttempt = context.GetJobParameter<int>("RetryCount") + 1;

        if (retryAttempt <= Attempts)
        {
            ScheduleAgainLater(context, retryAttempt, failedState);
        }
        else if (retryAttempt > Attempts && OnAttemptsExceeded == AttemptsExceededAction.Delete)
        {
            TransitionToDeleted(context, failedState);
        }
        else
        {
            if (LogEvents)
            {
                Logger.ErrorException(
                    String.Format(
                        "Failed to process the job '{0}': an exception occurred.",
                        context.JobId),
                    failedState.Exception);
            }
        }
    }

    /// <summary>
    /// Schedules the job to run again later. See <see cref="SecondsToDelay"/>.
    /// </summary>
    /// <param name="context">The state context.</param>
    /// <param name="retryAttempt">The count of retry attempts made so far.</param>
    /// <param name="failedState">Object which contains details about the current failed state.</param>
    private void ScheduleAgainLater(ElectStateContext context, int retryAttempt, FailedState failedState)
    {
        context.SetJobParameter("RetryCount", retryAttempt);

        var delay = TimeSpan.FromSeconds(SecondsToDelay(retryAttempt));

        const int maxMessageLength = 50;
        var exceptionMessage = failedState.Exception.Message;

        // If attempt number is less than max attempts, we should
        // schedule the job to run again later.
        context.CandidateState = new ScheduledState(delay)
        {
            Reason = String.Format(
                "Retry attempt {0} of {1}: {2}",
                retryAttempt,
                Attempts,
                exceptionMessage.Length > maxMessageLength
                ? exceptionMessage.Substring(0, maxMessageLength - 1) + "…"
                : exceptionMessage)
        };

        if (LogEvents)
        {
            Logger.WarnException(
                String.Format(
                    "Failed to process the job '{0}': an exception occurred. Retry attempt {1} of {2} will be performed in {3}.",
                    context.JobId,
                    retryAttempt,
                    Attempts,
                    delay),
                failedState.Exception);
        }
    }

    /// <summary>
    /// Transition the candidate state to the deleted state.
    /// </summary>
    /// <param name="context">The state context.</param>
    /// <param name="failedState">Object which contains details about the current failed state.</param>
    private void TransitionToDeleted(ElectStateContext context, FailedState failedState)
    {
        context.CandidateState = new DeletedState
        {
            Reason = String.Format("Automatic deletion after retry count exceeded {0}", Attempts)
        };

        if (LogEvents)
        {
            Logger.WarnException(
                String.Format(
                    "Failed to process the job '{0}': an exception occured. Job was automatically deleted because the retry attempt count exceeded {1}.",
                    context.JobId,
                    Attempts),
                failedState.Exception);
        }
    }

    // delayed_job uses the same basic formula
    private static int SecondsToDelay(long retryCount)
    {
        var random = new Random();
        return (int)Math.Round(
            Math.Pow(retryCount - 1, 4) + 15 + (random.Next(30) * (retryCount)));
    }

    public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        if (context.NewState is ScheduledState &&
            context.NewState.Reason != null &&
            context.NewState.Reason.StartsWith("Retry attempt"))
        {
            transaction.AddToSet("retries", context.JobId);
        }
    }

    public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
    {
        if (context.OldStateName == ScheduledState.StateName)
        {
            transaction.RemoveFromSet("retries", context.JobId);
        }
    }
}

关于c#-4.0 - 针对特定异常的 Hangfire 自动重试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51047728/

相关文章:

azure - 当他们从azure redis缓存中清除时,Hangfire完成了工作?

c# - TestRunType 的序列化抛出异常

.net-4.0 - 有没有办法知道任务并行库中当前正在运行哪些任务?

c# - Hangfire 显示在同一台服务器上运行的两个实例 v.1.5.3 - 导致错误

c# - Hangfire:从关键部分内排队后台作业

c# - 如何强制 hangfire 服务器在重启时删除该特定服务器的旧服务器数据?

c# - 将许多文本文件读取到字符串数组中 C#

c# - linq返回使用元组

.net - 有没有办法/工具来显示系统中所有的内存映射文件?

使用接口(interface)排队的 Hangfire 作业忽略类/方法级别的指定作业过滤器