æ°å¹ŽåãPhil Haackã¯ASP.NETã§ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãè¡ãããšã®å±éºæ§ã«é¢ããåªããèšäºãæžããŸãã ã 圌ã¯ãããã¯ã°ã©ãŠã³ãããã»ã¹ã®éå§ã«é¢é£ãã3ã€ã®äž»ãªãªã¹ã¯ãç¹å®ããŸããã
- èŠæ±ã«é¢é£ä»ããããŠããªãã¹ã¬ããã®æªåŠçã®äŸå€ã«ãããããã»ã¹ãåé€ãããå ŽåããããŸãã
- Webãã¡ãŒã ã§ãµã€ããå®è¡ãããšãåãã¿ã¹ã¯ã®è€æ°ã®ã€ã³ã¹ã¿ã³ã¹ãåæã«å®è¡ããå¯èœæ§ã®ããã¢ããªã±ãŒã·ã§ã³ã®è€æ°ã®ã€ã³ã¹ã¿ã³ã¹ã誀ã£ãŠçµäºããå¯èœæ§ããããŸãã
- ãµã€ããå®è¡ãããŠããã¢ããªã±ãŒã·ã§ã³ãã¡ã€ã³ã¯ãããŸããŸãªçç±ã§ããµã€ãã§å®è¡ãããŠããããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãã¢ã³ããŒãããã³åé€ã§ããŸãã
ãã¡ãããããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ã管çããç¬èªã®ãããŒãžã£ãŒãäœæã§ããŸãã ããããã»ãšãã©ã®å Žåãããªãã¯ééã£ãŠããŸãã éçºè ã®ã¹ãã«ã«ææŠãã人ã¯ããŸããã ãã®ãããªãããŒãžã£ãŒãäœæããããšã¯ãããªã埮åŠãªããšã§ãã ãããŠããªããããå¿ èŠãªã®ã§ããïŒ
ããã¯ã°ã©ãŠã³ãã§ã¿ã¹ã¯ãå®è¡ããããã®å€ãã®çŽ æŽããããªãã·ã§ã³ããããŸãã ãããŠãæœè±¡çãªæè¡ã ãã§ãªããæ¢è£œã®ã©ã€ãã©ãªããããŸãã
ASP.NETã¢ããªã±ãŒã·ã§ã³ã«ã¯ãIISã®äžã®ç¬èªã®ãµãŒããŒã§å®è¡ã§ãããã®ãšãAzureã§ãã¹ãã§ãããã®ããããŸãã
ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãéå§ããã«ã¯ãããã€ãã®ãªãã·ã§ã³ãéžæã§ããŸãã
- åºæ¬ïŒç¬èªã®ASP.NETãµã€ãã§ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãäœæããããã«äœ¿çšã§ããHangfireãŸãã¯åæ§ã®ã©ã€ãã©ãªã
- ã¯ã©ãŠãïŒ Azure WebJobs Azure Web Jobsã Webãµã€ãã®å€éšã§ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãå®è¡ããè² è·ã調æŽã§ããAzureã®ãã€ãã£ããµãŒãã¹ã
- é«åºŠïŒ ã¯ã©ãŠããµãŒãã¹ã®Azure WebããŒã« ã ãµã€ãã«é¢ä¿ãªãããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãå®è¡ã§ãããµãŒãã¹ãè² è·ã調æŽããå®è¡ãå¶åŸ¡ã§ããŸãã
Azureã¿ã¹ã¯ã䜿çšããæ¹æ³ãšãAzureã¯ã©ãŠããµãŒãã¹ã§WebããŒã«ãã©ã®ããã«æ©èœãããã«ã€ããŠãWebäžã«å€æ°ã®èšäºãšãããªããããŸãã ãã ããèªåã®ãµãŒããŒã§ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãå®è¡ããæ¹æ³ã«é¢ããèšäºã¯ããŸããããŸããã
WEBBACKGROUNDER
ãµã€ããèšãããã«ïŒãWebBackgrounderã¯ãASP.NET Webã¢ããªã±ãŒã·ã§ã³ã§ã®ã¿åäœããWebãã¡ãŒã äºæã®ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãããŒãžã£ãŒã®æŠå¿µã®ãã¹ãã§ããã ã³ãŒãã¯é·å¹Žå€æŽãããŠããŸãããã NuGetããã±ãŒãžã¯ 50äžå以äžããŠã³ããŒããããŠããŸãã
ãã®ãããžã§ã¯ãã䜿çšãããšã1ã€ã®ã¿ã¹ã¯ã®ã¿ã§äœæ¥ããWebã¢ããªã±ãŒã·ã§ã³ã®å®è¡äžã«ããã¯ã°ã©ãŠã³ãã§ç¹°ãè¿ãã¿ã¹ã¯ã管çã§ããŸãã
ãã®ã©ã€ãã©ãªã¯æããã«ãã¹ãŠã®å Žåã«é©ããŠããããã§ã¯ãããŸããã ãã ããASP.NETã¢ããªã±ãŒã·ã§ã³ã®å®è¡äžã«1ã€ã®ã¿ã¹ã¯ã®ã¿ãå®è¡ããå¿ èŠãããå Žåã¯ãWebBackgrounderã§ååã§ãã
using System; using System.Threading; using System.Threading.Tasks; namespace WebBackgrounder.DemoWeb { public class SampleJob : Job { public SampleJob(TimeSpan interval, TimeSpan timeout) : base("Sample Job", interval, timeout) { } public override Task Execute() { return new Task(() => Thread.Sleep(3000)); } } }
.NET 4.5.2 QUEUEBACKGROUNDWORKITEMã§è¿œå
QueueBackgroundWorkItemã¯ãWebBackgrounderã®åºçŸã«å¿ããŠ.NET 4.5.2ã«è¿œå ããããšèšããŸãã ããã¯Task.Runã®åãªãå®è£ ã§ã¯ãããŸããã ããã¯ãã£ãšäœãã§ãïŒ
QBWIã¯ããªã¯ãšã¹ãã«é¢ä¿ãªããããã¯ã°ã©ãŠã³ãã§å®è¡ãããã¹ã±ãžã¥ãŒã«ãããã¿ã¹ã¯ã管çããŸãã éåžžã®ThreadPoolèŠçŽ ãšã®éãã¯ãASP.NETãçŸåšAPIã䜿çšããŠç»é²ãããŠããäœæ¥é ç®ã®æ°ãèªåçã«è¿œè·¡ããã¢ããªã±ãŒã·ã§ã³ãã¡ã€ã³ããªãã«ãªã£ãŠããå ŽåãASP.NETã©ã³ã¿ã€ã ãããã延æããããšããå®è¡äžã®ããã¯ã°ã©ãŠã³ããã·ã£ããããŠã³ã§ããããšã§ãã¿ã¹ã¯ã
ASP.NETã©ã³ã¿ã€ã ã¯ãã¢ããªã±ãŒã·ã§ã³ãã¡ã€ã³ã®ã·ã£ããããŠã³ã90ç§ã ãé ãããããšãã§ããã¿ã¹ã¯ãå®äºããããšãã§ããããšã«æ³šæããŠãã ããã ãããã£ãŠããã®æéå ã«ã¿ã¹ã¯ãå®äºã§ããªãå Žåã¯ãä»ã®ããä¿¡é Œæ§ã®é«ãæ段ã䜿çšããå¿ èŠããããŸãã
APIã¯éåžžã«åçŽã§ã-ãFunc <CancellationTokenãTask>ãã䜿çšããŸãã 以äžã¯ãMVCããããã¯ã°ã©ãŠã³ãããã»ã¹ãéå§ããå°ããªäŸã§ãã
public ActionResult SendEmail([Bind(Include = "Name,Email")] User user) { if (ModelState.IsValid) { HostingEnvironment.QueueBackgroundWorkItem(ct => SendMailAsync(user.Email)); return RedirectToAction("Index", "Home"); } return View(user); }
FLUENTSCHEDULER
FluentSchedulerã¯ãæµadvancedãªã€ã³ã¿ãŒãã§ã€ã¹ãåãããããé«åºŠã§æŽç·Žãããã¿ã¹ã¯ãããŒãžã£ãŒã§ãã ããã«ãããã¿ã¹ã¯ãèµ·åããããã»ã¹ãå®éã«å¶åŸ¡ã§ããŸãã
using FluentScheduler; public class MyRegistry : Registry { public MyRegistry() { // ITask Schedule<MyTask>().ToRunNow().AndEvery(2).Seconds(); // Schedule(() => Console.WriteLine("Timed Task - Will run every day at 9:15pm: " + DateTime.Now)).ToRunEvery(1).Days().At(21, 15); // â Schedule(() => { Console.WriteLine("Complex Action Task Starts: " + DateTime.Now); Thread.Sleep(1000); Console.WriteLine("Complex Action Task Ends: " + DateTime.Now); }).ToRunNow().AndEvery(1).Months().OnTheFirst(DayOfWeek.Monday).At(3, 0); } }
FluentSchedulerã¯IoCããµããŒãããŠãããITaskFactoryã€ã³ã¿ãŒãã§ã€ã¹ãå®è£ ããã ãã§ããæ°ã«å ¥ãã®Dependency Injectionã©ã€ãã©ãªã䜿çšããŠç°¡åã«æ¥ç¶ã§ããŸãã
FluentSchedulerã¯.NET Coreã§åäœããããšã«æ³šæããŠãã ããã
ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ã§çºçããäŸå€ãåŠçããã«ã¯ãJobManagerãªããžã§ã¯ãã®JobExceptionã€ãã³ãã䜿çšã§ããŸãã ãã®ã€ãã³ãã«ãããã¿ã¹ã¯ã§çºçããäŸå€ã«é¢ããæ å ±ãååŸã§ããŸãã
JobManager.JobException += (info) => Log.Fatal("An error just happened with a scheduled job: " + info.Exception);
QUARTZ.NET
Quartz.NETã¯ã.NETã«å®è£ ãããŠããäžè¬çãªJavaãã¬ãŒã ã¯ãŒã¯ã®ããŒãžã§ã³ã§ãã ãã¬ãŒã ã¯ãŒã¯ã¯ç©æ¥µçã«éçºäžã§ãã ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãäœæããããã«ãQuartzã¯IJobã€ã³ã¿ãŒãã§ã€ã¹ãšå¯äžã®Executeã¡ãœããã䜿çšããŸããããã¯å®è£ ããå¿ èŠããããŸãã
using Quartz; using Quartz.Impl; using System; namespace ScheduledTaskExample.ScheduledTasks { public class JobScheduler { public static void Start() { IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler(); scheduler.Start(); IJobDetail job = JobBuilder.Create<MyJob>().Build(); ITrigger trigger = TriggerBuilder.Create() .WithDailyTimeIntervalSchedule (s => s.WithIntervalInHours(24) .OnEveryDay() .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(0, 0)) ) .Build(); scheduler.ScheduleJob(job, trigger); } } }
Application_Startå ã§ãããŒãžã£ãŒãèµ·åããã«ã¯ãJobScheduler.StartïŒïŒãåŒã³åºãå¿ èŠããããŸãã éå§ããã«ã¯ã Scheduled Actionsããã³Quartz.NET ïŒ Quartz.Netã䜿çšããASP.NETã®Scheduled Tasksã®ç¿»èš³ïŒãèªãããšãã§ããŸãã
ãã®ãããžã§ã¯ãã«ã¯ããªããŸãšããªããã¥ã¡ã³ããããã䜿çšãéå§ããåã«èªã䟡å€ããããŸãïŒå°ãªããšãã Quartzã䜿çšããéçºã®ãã¥ãŒããªã¢ã«ãã芧ãã ãã ïŒã
ãã³ãã¡ã€ã€ãŒ
ãããŠãæåŸã®ã¬ãã¥ãŒã§ãããæ©èœæ§ã®é¢ã§ã¯ééããªãããã¹ãŠã®Hangfireã®äžã§æãå é²çã§ãã ããã¯ãASP.NETã®éåžžã«é«åºŠãªããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãã¬ãŒã ã¯ãŒã¯ã§ãã ãªãã·ã§ã³ã§ãRedisãSQL ServerãSQL AzureãMSMQããŸãã¯RabbitMQã䜿çšããŠãã¿ã¹ã¯å®è¡ã®ä¿¡é Œæ§ãé«ããããšãã§ããŸãã
Hangfireã®ããã¥ã¡ã³ãã¯æ¬åœã«åªããŠããŸãã ãã¹ãŠã®ãªãŒãã³ãœãŒã¹ãããžã§ã¯ãã«ãã®ãããªããã¥ã¡ã³ããæããããã
Hangfireã®æãçŽ æŽãããæ©èœã®1ã€ã¯çµã¿èŸŒã¿ã®ããã·ã¥ããŒãã§ãã¹ã±ãžã¥ãŒã«ãå®äºããã¿ã¹ã¯ãæåããã¿ã¹ã¯ãšå€±æããã¿ã¹ã¯ã衚瀺ã§ããŸãã
Hangfireã䜿çšãããšããå®è¡ããŠå¿ããããªã©ã®ã¿ã¹ã¯ãç°¡åã«èå¥ã§ããŸããã¿ã¹ã¯ã«é¢ããæ å ±ã¯ããŒã¿ããŒã¹ã«ä¿åãããŸãã
BackgroundJob.Enqueue(() => Console.WriteLine("Fire-and-forget"));
ã¿ã¹ã¯ãé ãããããšãã§ããŸãïŒ
BackgroundJob.Schedule(() => Console.WriteLine("Delayed"), TimeSpan.FromDays(1));
ãŸãã¯ãCRONã¹ã¿ã€ã«ã§ã¿ã¹ã¯ãå®è¡ããŸã
RecurringJob.AddOrUpdate(() => Console.Write("Recurring"), Cron.Daily);
Hangfireã§ã®äœæ¥ã¯éåžžã«äŸ¿å©ã§ãã Hangfireã«ã¯ãå®éã®äŸã«åºã¥ããåªããããã¥ã¡ã³ããšãã¥ãŒããªã¢ã«ããããŸãã
Hangfireã¯ãASP.NETã¢ããªã±ãŒã·ã§ã³ã®ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãæäœããããã®ãšã³ã·ã¹ãã å šäœã§ãã
ã©ã€ãã©ãªã¯ããªãŒãã³ãœãŒã¹ãŸãã¯ãã²ããããã±ãŒãžãšããŠå ¥æã§ããŸãã
çµæïŒå人çã«ç§ããïŒ
èªåçšã®ã©ã€ãã©ãªãéžæããäžæ¹ã§ãé©åãªæ©èœãå¿ èŠã«ãªããäžæ¹ã§ãããšãã°å®è¡äžã®ã¿ã¹ã¯ã«é¢ããæ å ±ãä¿åããããã«ããŒã¿ããŒã¹ã䜿çšããããããŸããã ãã®ãããWebBackgrounderãQueueBackgroundWorkItemãªã©ã®åçŽãªãœãªã¥ãŒã·ã§ã³ãæ€èšããŸããã§ããã
è€æ°ã®ããã»ã¹ãéå§ããå¿ èŠãããããšã¯æ¢ã«ç¥ã£ãŠããŸããããã»ã¹ã¯é·æéïŒQueueBackgroundWorkItemã§ã®å®äºã90ç§ã«å¶éãããïŒåäœããå¯èœæ§ããããŸãã FluentSchedulerã¯ããããã§ããããã£ãšæ¬²ããã£ãã§ãã Hangfireã¯åªãããœãªã¥ãŒã·ã§ã³ã§ãããã¿ã¹ã¯ãã¥ãŒãæ ŒçŽããã«ã¯ããŒã¿ããŒã¹ãããã«äœ¿çšããå¿ èŠãããããã§ãã ã¯ããå®å šã«ç¡æã§ã¯ãããŸãããææçããããŸãã
æçµçã«ã¯ããããŸã§ã®ãšããQuartz.NETãéžæããŸãããããªããŸãšããªããã¥ã¡ã³ããããã«äœ¿ãå§ããã®ã«ååãªäŸãããã³ããŒãºã®å¢å ã«å¿ããŠè¿œå ã§ããæ¡åŒµå¯èœãªæ©èœã§ãã
ããã¯ã°ã©ãŠã³ãã¿ã¹ã¯ãèµ·åããããã®ä»ã®ã©ã€ãã©ãªãç¥ã£ãŠãããããã®ãããªåé¡ã解決ããçµéšãããå Žåã¯ãã³ã¡ã³ããå ±æããŠãã ããã