当Quartz作业与“对象引用未设置为对象的实例”一起运行时发生错误

时间:2018-09-17 01:44:26

标签: c# asp.net quartz-scheduler quartz.net

我有一个import sys sys.version_info = "boo" print(sys.version_info) # boo sys.modules.pop("sys") import sys # reloaded print(sys.version_info) # Output: sys.version_info(major=3, minor=6, ... 的{​​{1}}期间设置的Quartz作业

Application_Start

这将调用Global.asax.cs方法,该方法然后计划作业。

作业每20秒运行一次,并引发异常。这是我的工作。

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    Logger.log("About to Setup Retry Job");
    JobScheduler.Start();
}

此行Start引发异常。这是一个“静态”方法,可打开一个日志文件并将其写入。此方法可在我网站的其他任何地方使用。

以下是异常消息: {“对象引用未设置为对象的实例。”}

InnerException为NULL。这是堆栈:

public class RetryTempJob : IJob
{
    public async Task Execute(IJobExecutionContext context)
    {
        try
        {
            Logger.log("Executing Job");
            new ProcessOrder().retryFailedOrders();
            //Logger.log("Done Executing Syspro Job");
            await Console.Error.WriteLineAsync("Done Executing Syspro Job");
        }
        catch (Exception se)
        {
            await Console.Error.WriteLineAsync("" + se.InnerException);
        }
    }
}

这是我的Logger类代码

Logger.log("Executing Job");

2 个答案:

答案 0 :(得分:3)

鉴于记录器可以在请求内调用,也可以在请求外调用。如果作业开始并且没有请求,HttpContext将为 null

考虑将记录器与HttpContext之类的实现问题脱钩,并抽象出映射路径和利用依赖项注入的过程

public interface IPathProvider {
    string MapPath(string path);
}

您还应该避免静态记录器代码的气味,因为这会使代码难以维护和孤立地进行测试。

public interface ILogger {
    void log(string message);
    //...
}

public class Logger : ILogger {
    private readonly IPathProvider pathProvider;

    public Logger(IPathProvider pathProvider) {
        this.pathProvider = pathProvider;
    }

    public void log(string strLog) {
        FileStream fileStream = null;
        string username = Environment.UserName;
        string logFilePath = pathProvider.MapPath("~/log/Log.txt");
        var logFileInfo = new FileInfo(logFilePath);
        var logDirInfo = new DirectoryInfo(logFileInfo.DirectoryName);
        double fileSize = ConvertBytesToMegabytes(logFileInfo.Length);
        if (fileSize > 30) {
            string FileDate = DateTime.Now.ToString().Replace("/", "-").Replace(":", "-");
            string oldfilepath = pathProvider.MapPath("~/log/log-" + FileDate + ".txt");
            File.Move(logFileInfo.FullName, oldfilepath);
        }
        if (!logFileInfo.Exists) {
            fileStream = logFileInfo.Create();
        } else {
            fileStream = new FileStream(logFilePath, FileMode.Append);
        }
        using(fileStream) {
            using (var log = new StreamWriter(fileStream)) {
                log.WriteLine(DateTime.Now.ToString("MM-dd HH:mm:ss") + " " + username + " " + strLog);
                log.Close();
            }
        }
    }
}

并使工作明确取决于抽象。

public class RetryTempJob : IJob {
    private readonly ILogger logger;

    public RetryTempJob(ILogger logger) {
        this.logger = logger;
    }

    public async Task Execute(IJobExecutionContext context) {
        try {
            logger.log("Executing Job");
            new ProcessOrder().retryFailedOrders();
            //logger.log("Done Executing Syspro Job");
            await Console.Error.WriteLineAsync("Done Executing Syspro Job");
        } catch (Exception se) {
            await Console.Error.WriteLineAsync("" + se.InnerException);
        }
    }
}

这里可以抽象出更多内容,但这超出了示例范围。

现在已经解决了设计问题,我们可以看看路径提供程序的实现来解决HttpContext的情况。

  

Server.MapPath()要求HttpContext,而HostingEnvironment.MapPath不需要。

引用What is the difference between Server.MapPath and HostingEnvironment.MapPath?

该实现可以尝试检查上下文是否为空,但是Server.MapPath()最终会调用HostingEnvironment.MapPath(),因此最好只使用HostingEnvironment.MapPath()

public class PathProvider : IPathProvider {
    public string MapPath(string path) {
        return HostingEnvironment.MapPath(path);
    }
}

现在剩下的是配置调度程序以允许依赖项注入,并让您决定要使用哪个DI框架。

创建一个从默认Quartz.NET作业工厂SimpleJobFactory继承的作业工厂。这个新的作业工厂将在其构造函数中使用IServiceProvider,并覆盖NewJob()提供的默认SimpleJobFactory方法。

class MyDefaultJobFactory : SimpleJobFactory {
    private readonly IServiceProvider container;

    public MyDefaultJobFactory(IServiceProvider container) {
        this.container = container;
    }

    public override IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) {
        IJobDetail jobDetail = bundle.JobDetail;
        Type jobType = jobDetail.JobType;
        try {
            // this will inject any dependencies that the job requires
            return (IJob) this.container.GetService(jobType); 
        } catch (Exception e) {
            var errorMessage = string.Format("Problem instantiating job '{0}'.", jobType.FullName);
            throw new SchedulerException(errorMessage, e);
        }
    }
}

有许多DI框架可供选择,但是对于本示例,我使用的是.Net Core依赖注入扩展,由于.Net Core具有模块化性质,因此可以轻松地将其放入您的项目中。

最后,在应用程序启动时配置服务集合

protected void Application_Start() {
    AreaRegistration.RegisterAllAreas();
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    var services = new ServiceCollection();
    var serviceProvider = ConfigureService(services);
    var logger = serviceProvider.GetService<ILogger>();
    logger.log("About to Setup Retry Job");
    var jobScheduler = serviceProvider.GetRequiredService<IScheduler>();

    //...add jobs as needed
    jobScheduler.ScheduleJob(.....);

    //and start scheduler
    jobScheduler.Start();
}

private IServiceProvider ConfigureService(IServiceCollection services) {
    //Add job dependencies
    services.AddSingleton<ILogger, Logger>();
    services.AddSingleton<IPathProvider, PathProvider>();
    //add scheduler
    services.AddSingleton<IScheduler>(sp => {
        var scheduler = new StdSchedulerFactory().GetScheduler();
        scheduler.JobFactory = new MyDefaultJobFactory(sp);
        return scheduler;
    });

    //...add any other dependencies

    //build and return service provider
    return services.BuildServiceProvider();
}

正如我之前所说,您可以使用任何其他DI / IoC容器。现在,重构的代码足够灵活,您只需将其包装在派生的IServiceProvider类中,然后将其提供给作业工厂即可将它们交换。

通过清除使代码变味的担忧,您可以更轻松地对其进行管理。

答案 1 :(得分:2)

使用Quarz Job时没有HttpContext。它运行我一个单独的线程。因此,在具有Quarz Job的网站上,我使用HostingEnvironment

如此

HttpContext.Current.Server.MapPath("~/log/Log.txt")

使用

using System.Web.Hosting;

HostingEnvironment.MapPath("~/log/Log.txt");