اجرای وظایف زمان بندی شده با Quartz.NET - قسمت اول
نویسنده: بهروز راد
تاریخ: ۱۳۹۱/۰۵/۲۴ ۱۰:۱۳
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
namespace SchedulerDemo.Jobs
{
using System;
using System.IO;
using Quartz;
public class HelloJob : IJob
{
public void Execute(IJobExecutionContext context)
{
// for web apps
// string path = System.Web.Hosting.HostingEnvironment.MapPath("~/Data/Log.txt");
// for desktop apps
string path = @"C:\Log.txt";
using (StreamWriter sw = new StreamWriter(path, true))
{
sw.WriteLine("Message from HelloJob " + DateTime.Now.ToString());
}
}
}
}
namespace SchedulerDemo.Interfaces
{
public interface ISchedule
{
void Run();
}
}
حال، پیاده سازی خود را برای این interface ارائه میدهیم.
namespace SchedulerDemo.Jobs
{
using System;
using Quartz;
using Quartz.Impl;
using SchedulerDemo.Interfaces;
using SchedulerDemo.Jobs;
public class HelloSchedule : ISchedule
{
public void Run()
{
//DateTimeOffset startTime = DateBuilder.NextGivenSecondDate(null, 2);
DateTimeOffset startTime = DateBuilder.FutureDate(2, IntervalUnit.Second);
IJobDetail job = JobBuilder.Create<HelloJob>()
.WithIdentity("job1")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger1")
.StartAt(startTime)
.WithSimpleSchedule(x => x.WithIntervalInSeconds(10).WithRepeatCount(2))
.Build();
ISchedulerFactory sf = new StdSchedulerFactory();
IScheduler sc = sf.GetScheduler();
sc.ScheduleJob(job, trigger);
sc.Start();
}
}
}
معرفی فضاهای نام Quartz و Quartz.Impl را فراموش نکنید.
از حالا، به روالی که قرار است به صورت زمانبندی شده اجرا شود، "وظیفه" میگوییم.
ابتدا باید مشخص کنیم که وظیفه در چه زمانی پس از اجرای برنامه شروع به اجرا کند. از آنجا که پایه و اساس زمانبندی، بر تاریخ و ساعت استوار است، کتابخانهی Quartz.NET، روشها و امکانات بسیاری را برای تعیین زمان در اختیار قرار میدهد. با بررسی تمامی آنها، سادهترین و منعطفترین را به شما معرفی میکنم. کلاس DateBuilder که همراه با Quartz.NET وجود دارد، امکان تعیین زمان را به اَشکال مختلف میدهد. در خط 14، از متد FutureDate این کلاس استفاده شده است که خوانایی بهتری نسبت به بقیهی متدها دارد. پارامتر اول این متد، عدد، و پارامتر دوم، واحد زمانی را میپذیرد.
DateTimeOffset startTime = DateBuilder.FutureDate(2, IntervalUnit.Second);
در اینجا، زمان آغاز وظیفه را 2 ثانیه پس از آغاز برنامه تعریف کرده ایم. واحدهای زمانی دیگر شامل میلی ثانیه، دقیقه، ساعت، روز، ماه، هفته و سال هستند. کلاس DateBuilder، متدهای مختلفی برای تعیین زمان را در اختیار قرار میدهد. تعیین زمان آغاز به روش دیگر را به صورت کامنت شده در خط 13 مشاهده میکنید.
وظیفهی ایجاد شده در خط 16 تا 18 معرفی شده است.
IJobDetail job = JobBuilder.Create<HelloJob>()
.WithIdentity("job1")
.Build();
پشتیبانی Quartz.NET از سینتکس fluent، کدنویسی را ساده و لذت بخش میکند. با استفاده از متد Create کلاس JobBuilder، وظیفه را معرفی میکنیم. متد Create، یک متد Generic است که نام کلاسی که اینترفیس IJob را پیاده سازی کرده است میپذیرد. یک نام را با استفاده از متد WithIdentity به وظیفه نسبت میدهیم (البته این کار، اختیاری است) و در انتها، متد Build را فراخوانی میکنیم. خروجی متد Build، از نوع IJobDetail است.
و حالا نوبت به تنظیمات زمان رسیده است. در Quartz.NET، این مرحله، "ایجاد trigger" نام دارد. خطوط 20 تا 24 به این کار اختصاص دارند.
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("trigger1")
.StartAt(startTime)
.WithSimpleSchedule(x => x.WithIntervalInSeconds(10).WithRepeatCount(2))
.Build();
ابتدا متد Create کلاس TriggerBuilder را فراخوانی میکنیم، سپس با استفاده از متد WithIdentity، یک نام به trigger اختصاص میدهیم (البته این کار، اختیاری است). با متد StartAt، زمان شروع وظیفه را که در ابتدا با استفاده از کلاس DateBuilder ایجاد کردیم تعیین میکنیم. مهمترین قسمت، تعیین دفعات و فواصل زمانی اجرای وظیفه است. همان طور که احتمالاً حدس زده اید، Quartz.NET مجموعه ای غنی از روشهای مختلف برای تعیین بازهی زمانی اجرا را در اختیار قرار میدهد. آسانترین راه، استفاده از متد WithSimpleSchedule است. با استفاده از یک عبارت Lambda که ورودی آن از نوع کلاس SimpleScheduleBuilder است، دفعات و فواصل زمانی اجرا را تعیین میکنیم. متد WithIntervalInSeconds، برای تعیین فواصل زمانی در بازهی ثانیه استفاده میشود. متد WithRepeatCount نیز برای تعیین دفعات اجرا است. وظیفهی ما، 3 مرتبه و در فواصل زمانی 10 ثانیه اجرا میشود. مطمئن باشید اشتباه نکردم! بله، سه مرتبه. تعداد دفعات اجرا برابر است با عددی که برای متد WithRepeatCount تعیین میکنید، به علاوهی یک. منطقی است، چون مرتبهی اول اجرا زمانی است که با استفاده از متد StartAt تعیین کرده اید. در پایان، متد Build را فراخوانی میکنیم. خروجی متد Build، از نوع ITrigger است.
آخرین کار (خطوط 26 تا 30)، ایجاد شی از اینترفیس IScheduler، فراخوانی متد ScheduleJob آن، و پاس دادن اشیای job و trigger که در قسمت قبل ایجاد شده اند به این متد است. در انتها، متد ()Start را برای آغاز وظیفه فراخوانی میکنیم.
ISchedulerFactory sf = new StdSchedulerFactory(); IScheduler sc = sf.GetScheduler(); sc.ScheduleJob(job, trigger); sc.Start();
حال شما یک وظیفه تعریف کرده اید که در هر جای برنامه به صورت زیر، قابل فراخوانی است.
ISchedule myTask = new HelloSchedule(); myTask.Run();
کتابخانه ای که با آن سر و کار داریم بسیار غنی است و امکانات بسیاری دارد. در قسمت بعد، با برخی امکانات دیگر این کتابخانه آشنا میشوید.
با
سلام و تشکر از مطب مفید شما
من
در مورد کارهای زمان بندی شده قبلا سرچ کردم و به این نتیجه رسیدم که دات نت تا
قبل از ورژن 4 ،استانداردی برای انجام کارهای زمان بندی شده نداشت ولی در ورژن 4
فضای نام system.threading.tasks.taskscheduler را ارائه داده است. در صورت امکان تفاوتهای میان این استاندارد و
مطالب فوق را بیان کنید
با
تشکر مجدد
ممنون از شما
void Application_Start(object sender, EventArgs e)
{
// Schedule Start
}
void Application_End(object sender, EventArgs e)
{
// Code that runs on application shutdown
var scheduler = new StdSchedulerFactory().GetScheduler();
scheduler.Shutdown(true);
DoWebRequest ("SiteAddress");
}
public string DoWebRequest(string url)
{
WebRequest request = WebRequest.Create(url);
request.Credentials = CredentialCache.DefaultCredentials;
HttpWebResponse response = (HttpWebResponse)(request.GetResponse());
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
string responseFromServer = reader.ReadToEnd();
reader.Close();
dataStream.Close();
response.Close();
return responseFromServer;
}
Could not load file or assembly 'Common.Logging' or one of its dependencies. This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.
[PersistJobDataAfterExecution]
[DisallowConcurrentExecution]
public class SendGroupEmailJob : IJobBase
{
private List<MailAddress> lstMails;
public void Execute(IJobExecutionContext context)
{
int result = 0;
if (context.JobDetail.JobDataMap["UserEmailList"] != null)
{
lstMails = context.JobDetail.JobDataMap["UserEmailList"] as List<MailAddress>;
if (lstMails.Count == 0)
{
context.Scheduler.UnscheduleJob(new TriggerKey(context.Trigger.Key.Name));
}
else
{
JobDataMap map = context.JobDetail.JobDataMap;
result = EmailHandler.Send(lstMails[0],
map.GetString("Subject"),
map.GetString("Body").Replace("[FullName]", lstMails[0].DisplayName).Replace("[Email]", lstMails[0].Address),
context.JobDetail.JobDataMap["Attachment"] as List<string>,
MailPriority.High,
true,
Encoding.UTF8,
DeliveryNotificationOptions.None,
map.GetString("SenderEmail"),
map.GetString("SenderName"),
map.GetString("BccEmail"),
map.GetString("Prefix"),
map.GetBoolean("IsSSL"),
map.GetBoolean("IsCredential"),
map.GetString("Server"),
map.GetInt("Port"),
map.GetInt("TimeOut"),
map.GetString("PassWord"));
lstMails.RemoveAt(0);
}
}
}
} 0 33 10 * * ?
namespace SchedulerDemo.Jobs
{
using System;
using System.Linq;
using System.IO;
using Quartz;
using System.Collections.Generic;
using System.Configuration;
[PersistJobDataAfterExecution]
[DisallowConcurrentExecution]
public class SendJob : IJob
{
public void Execute(IJobExecutionContext context)
{
using (var db = new DALModel.DALEntities())
{
byte status = (byte)AllEnums.Sms.Status.InProgress;
var item = db.SentBoxes.Where(p => p.Status == status && p.IsDeleted==false && p.UserInfo.IsDeleted==false && p.HasTime == true && p.SendInTime == false && p.SendDateX <= DateTime.Now).OrderBy(p=>p.Id).FirstOrDefault();
Cls_SMS.ClsSend sms_Batch = new Cls_SMS.ClsSend();
if (item != null)
{
decimal smsCount = 0;
if (item.UserInfo.CalculateType == Convert.ToByte(AllEnums.FinancialTransaction.CalculationUnit.Message))
{
smsCount = Convert.ToDecimal(Function.GetSmsCount(item.Price, item.UserId));
}
else
{
smsCount = Convert.ToDecimal(item.CorrectCount);
}
decimal adminCredit = Function.GetAdminCreditLink1000();
if (adminCredit != -1 && adminCredit >= smsCount)
{
if ((item.UserInfo.Credit - (item.UserInfo.LowCredit)) >= item.Price)
{
item.SendInTime = true;
db.SaveChanges();
string numberList = item.NumberList;
int position = item.NumberList.LastIndexOf(',');
numberList = item.NumberList.Substring(0, position);
List<string> receivers_List = new List<string>();
receivers_List = (numberList).Split(',').ToList();
string[] ret2 = new string[2];
string[] DestAdd = new string[receivers_List.Count];
DestAdd = receivers_List.ToArray();
ret2 = sms_Batch.SendSMS_Batch(item.Message, DestAdd, item.UserInfoSenderNumber.AllNumber.Number, ConfigurationManager.AppSettings["SmsUserNameLink1000"], ConfigurationManager.AppSettings["SmsPasswordLink1000"], ConfigurationManager.AppSettings["SmsIPAddressLink1000"], ConfigurationManager.AppSettings["SmsCompanyLink1000"], false, item.Id);
var sentBoxUpdate = db.SentBoxes.FirstOrDefault(p => p.Id == item.Id);
sentBoxUpdate.Status = Convert.ToByte(AllEnums.Sms.Status.Send);
sentBoxUpdate.FinancialTransactionId = db.FinancialTransactions.Where(p => p.UserId == item.UserId).Max(p => p.Id);
if (ret2 != null)
{
sentBoxUpdate.RetValue0 = ret2[0];
sentBoxUpdate.RetValue1 = ret2[1];
}
db.SaveChanges();
}
else
{
byte statusFailedForAccount = (byte)AllEnums.Sms.Status.FailedForAccount;
item.SendInTime = true;
item.Status = statusFailedForAccount;
item.FailedCount = item.CorrectCount;
item.FailedList = item.NumberList;
db.SaveChanges();
}
}
else
{
byte statusFaildForError = (byte)AllEnums.Sms.Status.FaildForError;
item.SendInTime = true;
item.Status = statusFaildForError;
item.FailedCount = item.CorrectCount;
item.FailedList = item.NumberList;
db.SaveChanges();
}
}
}
}
}
}
namespace SchedulerDemo.Interfaces
{
public interface IScheduleSend
{
void RunSendSms();
}
}
namespace SchedulerDemo.Jobs
{
using System;
using Quartz;
using Quartz.Impl;
using SchedulerDemo.Interfaces;
using SchedulerDemo.Jobs;
public class SendSchedule : IScheduleSend
{
public void RunSendSms()
{
DateTimeOffset startTime = DateBuilder.FutureDate(2, IntervalUnit.Second);
IJobDetail job = JobBuilder.Create<SendJob>()
.WithIdentity("jobSendSmsInTime")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("triggerSendSmsInTime")
.StartAt(startTime)
.WithSimpleSchedule(x => x.WithIntervalInMinutes(5).RepeatForever())
.Build();
ISchedulerFactory sf = new StdSchedulerFactory();
IScheduler sc = sf.GetScheduler();
sc.ScheduleJob(job, trigger);
sc.Start();
}
}
} var taskToRun = _tasks.Where(x => !x.IsRunning && x.RunAt(now)).OrderBy(x => x.Order).ToList();
protected void Application_Start(object sender, EventArgs e)
{
Interface1 myTask = new HelloSchedule();
myTask.Run();
} Message from HelloJob 6/19/2014 10:40:15 AM Message from HelloJob 6/19/2014 10:40:25 AM Message from HelloJob 6/19/2014 10:40:35 AM Message from HelloJob 6/19/2014 10:40:45 AM Message from HelloJob 6/19/2014 10:40:55 AM Message from HelloJob 6/19/2014 11:24:36 AM Message from HelloJob 6/19/2014 11:24:45 AM Message from HelloJob 6/19/2014 11:24:55 AM
Application_Start
using Quartz;
using System.Data;
using System.Data.OleDb;
using System.Configuration;
namespace WebApplication1
{
public class TestQuartzClass:IJob
{
public void Execute(IJobExecutionContext context)
{
//Page myPage = (Page)HttpContext.Current.Handler;
//TextBox MyTextBox=(TextBox)myPage.FindControl("txt");
string sql = "Insert into tbl_Test (Content) values (@Content)";
ExecuteNoneQuery(System.Data.CommandType.Text, sql, new OleDbParameter[]{
//new OleDbParameter("@Content", MyTextBox.Text)
new OleDbParameter("@Content","Hello world!")
});
}
public int ExecuteNoneQuery(CommandType commandType, string commandText, params OleDbParameter[] commandParameters)
{
using (OleDbConnection con = new OleDbConnection(ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString))
{
OleDbCommand cmd = new OleDbCommand();
cmd.Connection = con;
cmd.CommandType = commandType;
cmd.CommandText = commandText;
cmd.Parameters.AddRange(commandParameters);
con.Open();
int retVal = cmd.ExecuteNonQuery();
con.Close();
return retVal;
}
}
}
} using System;
using Quartz;
using Quartz.Impl;
namespace WebApplication1
{
public partial class WebForm1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
public static void ConfigureQuartzJobs()
{
// construct a scheduler factory
ISchedulerFactory schedFact = new StdSchedulerFactory();
// get a scheduler
IScheduler sched = schedFact.GetScheduler();
sched.Start();
IJobDetail job = JobBuilder.Create<TestQuartzClass>()
.WithIdentity("SendJob")
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity("SendTrigger")
.WithSimpleSchedule(x => x.WithIntervalInMinutes(1).RepeatForever())
//.StartAt(startTime)
.StartNow()
.Build();
sched.ScheduleJob(job, trigger);
}
protected void btn_Click(object sender, EventArgs e)
{
ConfigureQuartzJobs();
}
}
} new OleDbParameter("@Content","Hello world!") Page myPage = (Page)HttpContext.Current.Handler;
TextBox MyTextBox=(TextBox)myPage.FindControl("txt"); new OleDbParameter("@Content", MyTextBox.Text) ترد اجرایی یک وظیفهی پس زمینه با ترد اجرایی یک درخواست وب یکی نیست. بنابراین نمیتونید به اشیاء رابط کاربری در تردی دیگر دسترسی داشته باشید. در برنامههای دسکتاپ اگر اینکار را انجام بدید، برنامه آنی کرش میکنه. در وب هم فرقی نمیکنه؛ اصول یکی هست.
کاری که میخواهید انجام بدید کلا نباید از این طریق انجام بشه. اگر هدفتون مثلا پیاده سازی auto-save هست که در بعضی از سایتها دیدید که هر از چند ثانیه یکبار متن رو ذخیره میکنند، این رو با یک تایمر جاوا اسکریپتی سمت کاربر و Ajax انجام میدن و نه با یک وظیفهی پس زمینه.
یک جدول طراحی کنید به نام mass mail که در آن یک Content به علاوه فیلد IsDone وجود داره. مدیر سیستم متن خودش رو به صورت معمول در این جدول ثبت کنه. در وظیفهی پس زمینه، رکوردهایی را که IsDone آنها false است یکی یکی یافته و پردازش کنید. بعد از اتمام کار، IsDone هر رکورد را true کنید.
مزیت این روش اینه که دو ترد کاملا مجزای رابط کاربری و ترد وظیفهی پس زمینه، هیچ تداخل و تماسی با هم نخواهند داشت تا سبب کرش برنامه شوند.
var scheduler = new StdSchedulerFactory().GetScheduler();
scheduler.DeleteJob(new JobKey("SendJob")); protected void Button6_Click(object sender, EventArgs e)
{
var scheduler = new StdSchedulerFactory().GetScheduler();
var jobs = scheduler.GetCurrentlyExecutingJobs();
//foreach (var j in jobs)
//{
// Console.WriteLine("Progress of {0} is {1}",
// j.JobDetail.Key,
// j.JobDetail.JobDataMap["progress"]);
// Label620.Text += j.JobDetail.Key.ToString();
//}
if (jobs.Count ==0)
{
Label620.Text = "آماده برای ارسال ایمیل!";
}
else
{
Label620.Text = "لطفاً بعد از چند دقیقه مجدداً تلاش نمایید!";
}
} میتونی زمان اجرای کاری رو از قبل دقیق طراحی کنی. یک مثال: انجام کارهای زمانبندی شده در برنامههای ASP.NET توسط DNT Scheduler
dbContext.TechContext.SmsSendBoxes.InsertOnSubmit(new ContextFile.SmsSendBox()
{
Date = PersianDate.GetNowDate(),
Message = msg,
Res = e[0].ToString(),
Time = PersianDate.GetNowTime(),
Type = (int)Type,
Sender = set.DefSender
});
try { dbContext.TechContext.SubmitChanges(); }
catch (Exception er) { } System.Collections.ListDictionaryInternal Object reference not set to an instance of an object. App_Code
نظرات بالاتر رو خوندید یکبار؟ خلاصهاش اینه جهت تکرار:
«ترد اجرایی یک وظیفهی پس زمینه با ترد اجرایی یک درخواست وب یکی نیست. بنابراین نمیتونید به اشیاء رابط کاربری در تردی دیگر دسترسی داشته باشید. در برنامههای دسکتاپ اگر اینکار را انجام بدید، برنامه آنی کرش میکنه. در وب هم فرقی نمیکنه؛ اصول یکی هست.»
اگر dbContext مثلا در سطح فرم ایجاد شده، اون فرم زمانیکه وظیفهی شما قراره اجرا بشه، از بین رفته. وجود خارجی نداره. کار «پس زمینه» به همین معنا هست. dbContext رو داخل وظیفه باید وهله سازی کنید.
.WithIdentity("trigger12")
.StartAt(startTime)
.WithSimpleSchedule(x => x.WithIntervalInSeconds(30).RepeatForever())
.Build();