/// <summary> /// Runs this scheduler forever... scheduling workers for running, /// and sleeping for the amount of time indicated in the constructor. /// </summary> /// <param name="taskScheduler">The TaskScheduler to use to schedule tasks to be run.</param> private void Run([NotNull] TaskScheduler taskScheduler) { Action <string, Exception> logException = (typeName, ex) => { if (ex != null) { Trace.TraceError( string.Format( "IntervalWorkerScheduler.Run(taskScheduler): [Worker of type {1}] ended with exception: {0}", TraceHelper.GetExceptionMessage(ex), typeName)); } }; while (true) { try { // This foreach is instantaneous... it just schedules each worker to run using a taskScheduler. foreach (var eachWorker in this) { var task = new Task(eachWorker.RunOnce); task.Start(taskScheduler); // Observing Exception so that Task finalization does not rethrows it. // If Task finalization is allowed to rethrow exceptions, then process will die, // regardless of the try/catch block surrouding this method. // ReSharper disable ConditionIsAlwaysTrueOrFalse var typeName = eachWorker == null ? "[NULL]" : eachWorker.GetType().Name; // ReSharper restore ConditionIsAlwaysTrueOrFalse task.ContinueWith(t => logException(typeName, t.Exception)); } Thread.Sleep(this.timeSpan); } catch { // Never going to crash... so this is safe! } } // ReSharper disable FunctionNeverReturns }
/// <summary> /// Runs the worker once to send e-mails. /// </summary> public override void RunOnce() { // If this method is already running, then leave the already running alone, and return. // If it is not running, set the value os locker to 1 to indicate that now it is running. if (Interlocked.Exchange(ref locker, 1) != 0) { return; } Trace.TraceInformation("EmailSenderWorker.RunOnce(): service in execution"); var emailsSent = 0; try { var utcNow = this.GetUtcNow(); using (var db = this.CreateNewCerebelloEntities()) { Trace.TraceInformation("EmailSenderWorker.RunOnce(): this.CreateNewCerebelloEntities()"); var next40H = utcNow.AddHours(40.0); // One e-mail will be sent 40 hours before the appointment. var items = db.Appointments .Where(a => a.Start > utcNow && a.Start < next40H && !a.ReminderEmailSent) .Where(a => a.PatientId != null) .Select( a => new { Appointment = a, Practice = db.Practices.FirstOrDefault(p => p.Id == a.PracticeId), a.Patient, a.Patient.Person, a.Doctor, DoctorPerson = a.Doctor.Users.FirstOrDefault().Person, }) .Select( a => new { a.Appointment, EmailViewModel = new PatientEmailModel { PatientEmail = a.Person.Email, PatientName = a.Person.FullName, PracticeUrlId = a.Practice.UrlIdentifier, PatientGender = (TypeGender)a.Person.Gender, PracticeName = a.Practice.Name, DoctorName = a.DoctorPerson.FullName, DoctorGender = (TypeGender)a.DoctorPerson.Gender, AppointmentDate = a.Appointment.Start, PracticeEmail = a.Practice.Email, PracticePhoneMain = a.Practice.PhoneMain, PracticePhoneAlt = a.Practice.PhoneAlt, PracticeSiteUrl = a.Practice.SiteUrl, PracticePabx = a.Practice.PABX, PracticeAddress = new AddressViewModel { StateProvince = a.Practice.Address.StateProvince, City = a.Practice.Address.City, Neighborhood = a.Practice.Address.Neighborhood, Street = a.Practice.Address.Street, Complement = a.Practice.Address.Complement, CEP = a.Practice.Address.CEP, }, // todo: is it correct to send these informations to the patient // maybe this info is not suited to the outside world DoctorPhone = a.DoctorPerson.PhoneCell ?? a.DoctorPerson.PhoneLand, DoctorEmail = a.DoctorPerson.Email, } }) .ToArray(); Trace.TraceInformation("EmailSenderWorker.RunOnce(): got list of appointments from database"); bool traceMessageCreated = false; bool traceEmailSent = false; bool traceEmailNotSent = false; bool traceDbSaved = false; foreach (var eachItem in items) { if (string.IsNullOrWhiteSpace(eachItem.EmailViewModel.PatientEmail)) { continue; } // Rendering message bodies from partial view. var emailViewModel = eachItem.EmailViewModel; var toAddress = new MailAddress(emailViewModel.PatientEmail, emailViewModel.PatientName); var mailMessage = this.CreateEmailMessage("AppointmentReminderEmail", toAddress, emailViewModel); if (!traceMessageCreated) { Trace.TraceInformation("EmailSenderWorker.RunOnce(): var mailMessage = { rendered e-mail message }"); } traceMessageCreated = true; if (this.TrySendEmail(mailMessage)) { if (!traceEmailSent) { Trace.TraceInformation("EmailSenderWorker.RunOnce(): this.TrySendEmail(mailMessage) => true"); } traceEmailSent = true; emailsSent++; this.EmailsCount++; eachItem.Appointment.ReminderEmailSent = true; db.SaveChanges(); if (!traceDbSaved) { Trace.TraceInformation("EmailSenderWorker.RunOnce(): db.SaveChanges()"); } traceDbSaved = true; } else { if (!traceEmailNotSent) { Trace.TraceInformation("EmailSenderWorker.RunOnce(): this.TrySendEmail(mailMessage) => false"); } traceEmailNotSent = true; } } Trace.TraceInformation(string.Format("EmailSenderWorker.RunOnce(): emailsSent => {0}", emailsSent)); } } catch (Exception ex) { Trace.TraceError(string.Format("EmailSenderWorker.RunOnce(): exception while trying to send e-mails: {0}", TraceHelper.GetExceptionMessage(ex))); } // setting locker value to 0 if (Interlocked.Exchange(ref locker, 0) != 1) { throw new Exception("The value of locker should be 1 before setting it to 0."); } }