Ошибка при выполнении задания Quartz с «Ссылкой на объект, не установленной для экземпляра объекта» - PullRequest
0 голосов
/ 17 сентября 2018

У меня есть задание Quartz, настроенное во время Application_Start в Global.asax.cs

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

. Вызывается метод Start, который затем планирует задание.

Заданиезапускается каждые 20 секунд и выдает исключение.Вот моя работа.

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.log("Executing Job");.Это статический метод, который открывает файл журнала и пишет в него.Этот метод работает везде на моем сайте.

Вот сообщение об исключении: {"Ссылка на объект не установлена ​​на экземпляр объекта."}

InnerException - NULL.Вот стек:

DarwinsShoppingCart.dll!DarwinsShoppingCart.SharedClasses.JobScheduler.RetrySyspro.Execute(Quartz.IJobExecutionContext context) Line 69 C#
    Quartz.dll!Quartz.Core.JobRunShell.Run(System.Threading.CancellationToken cancellationToken)    Unknown
    mscorlib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start<Quartz.Core.JobRunShell.<Run>d__9>(ref Quartz.Core.JobRunShell.<Run>d__9 stateMachine)    Unknown
    Quartz.dll!Quartz.Core.JobRunShell.Run(System.Threading.CancellationToken cancellationToken)    Unknown
    Quartz.dll!Quartz.Core.QuartzSchedulerThread.Run.AnonymousMethod__0()   Unknown
    mscorlib.dll!System.Threading.Tasks.Task<System.Threading.Tasks.Task>.InnerInvoke() Unknown
    mscorlib.dll!System.Threading.Tasks.Task.Execute()  Unknown
    mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj)   Unknown
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot)    Unknown
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) Unknown
    mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() Unknown
    mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()    Unknown
    mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() Unknown

Вот мой код класса Logger

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

    log.WriteLine(DateTime.Now.ToString("MM-dd HH:mm:ss") + " " + username + " " + strLog);
    log.Close();
}

Ответы [ 2 ]

0 голосов
/ 21 сентября 2018

Учитывая, что регистратор может быть вызван в запросе, а также может быть вызван вне запроса.Если задание запущено и нет запроса, 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 - нет.

Ссылка В чем разница между Server.MapPath и 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 Dependency Injection Extension, который из-за модульной природы .Net Core позволяет легко добавить его в ваш проект.

Наконец, настройте набор служб в приложении.start

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 и передав его фабрике заданий.

И очистив код от проблем, которые делаютЗапах кода, вы можете управлять им проще.

0 голосов
/ 20 сентября 2018

При использовании задания Quarz нет HttpContext. Это работает в отдельном потоке. Так что на сайте с Quarz Job я использую HostingEnvironment

Таким образом,

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

Использование

using System.Web.Hosting;

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