Imports System.Collections.Specialized Imports System.Data.SqlClient Imports DigitalData.Modules.Database Imports DigitalData.Modules.Logging Imports Quartz Imports Quartz.Impl Imports Quartz.Impl.Matchers Imports Quartz.Logging Public Class Scheduler Inherits BaseClass Private ReadOnly _Factory As StdSchedulerFactory Private ReadOnly _SchedulerMSSQL As MSSQLServer ' ✅ Dedizierte Connection für alle Scheduler-Operationen Private _Scheduler As IScheduler Private ReadOnly _JobListener As JobListener Private ReadOnly _GlobalState As GlobalState Private ReadOnly _Props = New NameValueCollection From { {"quartz.serializer.type", "binary"}, {"quartz.threadPool.maxConcurrency", 2} ' ← von 3 auf 2 } Private Const JOB_GROUP As String = "DatatableJobs" Public Sub New(pLogConfig As LogConfig, pMSSQL_Config As MSSQLServer, pTableStore As DataSet, pGlobalState As GlobalState) MyBase.New(pLogConfig) _Factory = New StdSchedulerFactory(_Props) ' ✅ Separater Connection-Pool ausschließlich für Scheduler-Jobs ' Application Name weicht ab → ADO.NET legt eigenständigen Pool an ' → Scheduler-Queries blockieren nie die WCF-Verbindungen des Service Dim oBuilder As New SqlConnectionStringBuilder(pMSSQL_Config.CurrentConnectionString) oBuilder.ApplicationName = "EDMIService-Scheduler" oBuilder.MaxPoolSize = 2 ' ← NEU: begrenzt SQL Server-Last unter Volllast _SchedulerMSSQL = New MSSQLServer(pLogConfig, oBuilder.ToString()) Logger.Info("Scheduler: dedicated MSSQLServer instance created | AppName=[{0}]", oBuilder.ApplicationName) _JobListener = New JobListener(pLogConfig, _SchedulerMSSQL, pTableStore) _GlobalState = pGlobalState _GlobalState.JobListener = _JobListener Logger.Debug("JobListener registered in GlobalState") Logging.LogProvider.SetCurrentLogProvider(New LogProvider(Logger)) End Sub Public Async Sub Start() _Scheduler = Await _Factory.GetScheduler() _Scheduler.ListenerManager.AddJobListener(_JobListener, GroupMatcher(Of JobKey).GroupEquals(JOB_GROUP)) Await _Scheduler.Start() Dim oCronjobs As DataTable = Await GetCronJobs() Try If oCronjobs IsNot Nothing Then Logger.Debug("Loaded {0} cron jobs", oCronjobs.Rows.Count) For Each oRow As DataRow In oCronjobs.Rows Dim oDefinition As String = oRow.Item("CRON_DEFINITION") Dim oTitle As String = oRow.Item("TITLE") Dim oGuid As Integer = oRow.Item("GUID") Logger.Debug("Trying to schedule a new job forr Cron Job [{0}] ...", oTitle) Dim oCronTrigger As ITrigger Dim oInitTrigger As ITrigger Dim oJob As IJobDetail Dim oInitJob As IJobDetail Dim oJobData As New JobDataMap From { {"LogConfig", LogConfig}, {"MSSQL", _SchedulerMSSQL}, {"CronJobId", oGuid}, {"CronJobTitle", oTitle} } ' INIT-Job: gestaffelt starten – 15s Abstand statt 10s ' Bei maxConcurrency=2 laufen max. 2 Jobs gleichzeitig Dim oInitDelaySeconds As Integer = oCronjobs.Rows.IndexOf(oRow) * 15 Dim oIdentity As String = $"CRON-JOB-{oGuid}" oCronTrigger = TriggerBuilder.Create(). WithIdentity(oIdentity & "-TRIGGER", JOB_GROUP). WithCronSchedule(oDefinition). Build() oJob = JobBuilder.Create(Of DatatableJob)(). WithIdentity(oIdentity, JOB_GROUP). UsingJobData(oJobData). Build() Dim oNextFireTime As DateTimeOffset? = Await _Scheduler.ScheduleJob(oJob, oCronTrigger) Logger.Info("Cron Job [{0}] -> next scheduled run: {1}", oTitle, If(oNextFireTime.HasValue, oNextFireTime.Value.LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss"), "unknown")) ' Also create variant of the job that runs immediately, once Dim oInitIdentity As String = $"CRON-JOB-INIT-{oGuid}" oInitTrigger = TriggerBuilder.Create(). WithIdentity(oInitIdentity & "-TRIGGER", JOB_GROUP). StartAt(DateTimeOffset.Now.AddSeconds(oInitDelaySeconds)). ' ✅ war: StartNow() Build() oInitJob = JobBuilder.Create(Of DatatableJob)(). WithIdentity(oInitIdentity, JOB_GROUP). UsingJobData(oJobData). Build() Await _Scheduler.ScheduleJob(oInitJob, oInitTrigger) Logger.Debug("Scheduled a new job for Cron Job [{0}]", oTitle) Next Logger.Info("✔ Scheduler started with {0} cron jobs", oCronjobs.Rows.Count) Else Logger.Warn("⚠ CronJobs could not be fetched!") End If Catch ex As Exception Logger.Error(ex) Logger.Warn("⚠ Unexpected Error while setting up scheduler: " & ex.Message) End Try End Sub Public Async Function GetCronJobs() As Task(Of DataTable) Try Dim oSQL As String = "SELECT * FROM TBAPPSERV_CRON_JOB WITH (NOLOCK) WHERE ACTIVE = 1" Return Await _SchedulerMSSQL.GetDatatableAsync(oSQL) ' ✅ dedizierte Instanz Catch ex As Exception Return Nothing End Try End Function Public Async Function GetCronJobDetails(CronJobId As Integer) As Task(Of DataTable) Try Dim oSQL As String = $"SELECT * FROM TBAPPSERV_CRON_DETAIL WITH (NOLOCK) WHERE CRON_ID = {CronJobId}" Return Await _SchedulerMSSQL.GetDatatableAsync(oSQL) ' ✅ dedizierte Instanz Catch ex As Exception Return Nothing End Try End Function Public Async Sub [Stop]() Await _Scheduler.Shutdown() End Sub End Class