VB.NET定时器超时取消任务报错及Main代码执行问题咨询
问题排查与咨询:System.Timers.Timer超时取消任务异常及主线程代码执行疑问
我需要实现System.Timers.Timer超时时取消processingJob任务的功能,但在Tick方法中执行Await Task.WhenAll(jobCancelledList)时触发错误,请求协助排查;同时咨询:定时器触发后,Main方法中未执行的剩余代码是否会继续运行?
相关代码实现
定时器及任务列表定义
Private WithEvents sysTimer As New System.Timers.Timer Private ReadOnly joblist() As ProcessingJob
定时器超时处理方法
'Handling Timer Elapsed Private Sub Tick(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles sysTimer.Elapsed Try sysTimer.Stop() Dim jobCancelledList As New List(Of Task(Of Boolean))(joblist.Count) For Each listedJob As processingJob In joblist jobCancelledList.Add(listedJob.CancelAsync) Next Await Task.WhenAll(jobCancelledList) objErrorLogging.LogActivity("*** TASK TIMED OUT ***") Catch ex As Exception objErrorLogging.LogError(_CLASS_NAME, System.Reflection.MethodInfo.GetCurrentMethod().Name, ex.StackTrace, ex) objErrorLogging.LogActivity("*** TASK TIMED OUT ***") End Try End Sub
任务初始化方法
'Calling Internal Functions Private Async Function setupGPJob(ByVal url As String) As Task(Of processingJob) Try objErrorLogging.LogActivity(String.Format("Starting processing task {0}", url)) Dim processURI As New Uri(url) Dim streetCred As TokenCredential = Await AuthenticationManager.Current.GenerateCredentialAsync(processURI, ConfigurationManager.AppSettings("UserName"), ConfigurationManager.AppSettings("Password")) SyncLock _syncObject 'Adds the necessary Credentials object to the list of allowed objects so that the service can be accessed. AuthenticationManager.Current.AddCredential(streetCred) End SyncLock Dim task As processingTask = Await processingTask.CreateAsync(processURI) Dim newJob As processingJob = task.CreateJob(Await task.CreateDefaultParametersAsync) newJob.Start() Return newJob Catch ex As Exception objErrorLogging.LogError(_CLASS_NAME, System.Reflection.MethodInfo.GetCurrentMethod().Name, ex.StackTrace, ex) objErrorLogging.LogActivity("*** AN ERROR OCCURRED. ***") Return Nothing End Try End Function
任务执行主方法
'Function to complete tasks Private Async Function doprocessing(ByVal docast As Boolean) As Task(Of Boolean) Try 'Set up the list of processing jobs that need to be performed. objErrorLogging.LogActivity("Starting processing jobs") sysTimer.AutoReset = True sysTimer.Interval = (25 * 60000) '25 minutes interval sysTimer.Start() Dim jobListSetup As New List(Of Task(Of processingJob)) From {setupGPJob(ConfigurationManager.AppSettings("GPService1"))} If docast Then jobListSetup.Add(setupGPJob(ConfigurationManager.AppSettings("GPService2"))) Dim jobList() As processingJob = Await Task(Of processingJob).WhenAll(Of processingJob)(jobListSetup) 'Run through the list and set them up to await for them to run to completetion Dim jobResultsList As New List(Of Task(Of processingResult))(jobList.Count) For Each listedJob As processingJob In jobList jobResultsList.Add(listedJob.GetResultAsync) Next Await Task.WhenAll(jobResultsList) 'Run through the list of Jobs and check for Errors or Warnings. Log those accordingly. Dim results As Boolean = True For Each listedJob As processingJob In jobList If listedJob.Error IsNot Nothing Then objErrorLogging.LogError(_CLASS_NAME, System.Reflection.MethodInfo.GetCurrentMethod().Name, "Error encountered in processing Job", listedJob.Error) objErrorLogging.LogActivity("*** AN ERROR OCCURRED. ***") results = False Else For Each message As JobMessage In listedJob.Messages If message.Source = JobMessageSource.Service Then If message.Severity = JobMessageSeverity.Warning Then objErrorLogging.LogActivity("processing WARNING: " + message.Message) Else If message.Severity = JobMessageSeverity.Error Then Dim newError As New Exception(message.Message) objErrorLogging.LogError(_CLASS_NAME, System.Reflection.MethodInfo.GetCurrentMethod().Name, "Error encountered in processing Job", newError) objErrorLogging.LogActivity("*** AN ERROR OCCURRED. ***") results = False End If End If End If Next End If Next Return results Catch ex As Exception objErrorLogging.LogError(_CLASS_NAME, System.Reflection.MethodInfo.GetCurrentMethod().Name, ex.StackTrace, ex) objErrorLogging.LogActivity("*** AN ERROR OCCURRED. ***") Return False End Try End Function
程序入口Main方法
Sub Main() Dim blnDelete1 As Boolean = False Dim blnDelete2 As Boolean = False Try objErrorLogging.LogActivity("-- PROGRAM STARTS --") 'Get data into our DB blnDelete1 = import1Data() 'Get data into our DB blnDelete2 = import2Data() 'Do the Processing, set flag to True if run casts, o.w. set to False. If doprocessing(blnDelete2).Result Then objErrorLogging.LogActivity("processing operations successfully run to completion") System.GC.Collect() 'Wait for app to let go of files Threading.Thread.Sleep(10000) If blnDelete1 Then File.Delete(ConfigurationManager.AppSettings("FilePath1")) If blnDelete2 Then File.Delete(ConfigurationManager.AppSettings("FilePath2")) Else objErrorLogging.LogActivity("processing operations failed to run to completion. TO THE LOG FILES!") System.GC.Collect() 'Wait for app to let go of files Threading.Thread.Sleep(10000) If blnDelete1 Then File.Delete(ConfigurationManager.AppSettings("FilePath1")) If blnDelete2 Then File.Delete(ConfigurationManager.AppSettings("FilePath2")) End If Catch ex As Exception objErrorLogging.LogError(_CLASS_NAME, System.Reflection.MethodInfo.GetCurrentMethod().Name, ex.StackTrace, ex) objErrorLogging.LogActivity("*** AN ERROR OCCURRED. END IMPORT DATA WITH ERROR ***") End Try End Sub
报错截图


问题排查与解答
一、Await Task.WhenAll(jobCancelledList)异常排查
joblist未同步赋值
代码中joblist是只读数组,但doprocessing方法生成的jobList未同步到该成员变量,定时器触发时joblist为空,遍历会引发空引用异常。需将joblist改为List(Of processingJob)类型,并在doprocessing中完成任务初始化后赋值:'修改成员变量定义 Private ReadOnly joblist As New List(Of processingJob)() '在doprocessing方法中,Await Task.WhenAll(jobListSetup)后添加 joblist.Clear() joblist.AddRange(jobList)异步事件处理器声明问题
System.Timers.Timer的Elapsed事件处理器需显式声明为Async Sub(VB允许异步事件处理器),否则Await会导致异步代码脱离上下文执行,引发未捕获异常:Private Async Sub Tick(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles sysTimer.Elapsed '原逻辑不变 End Sub单个任务取消异常影响全局
需为每个CancelAsync调用添加单独异常捕获,避免单个任务取消失败导致整个Task.WhenAll终止:For Each listedJob As processingJob In joblist If listedJob IsNot Nothing Then jobCancelledList.Add(Task.Run(Async Function() Try Return Await listedJob.CancelAsync Catch ex As Exception objErrorLogging.LogError(_CLASS_NAME, "CancelAsync", "Failed to cancel job", ex) Return False End Try End Function)) End If Next
二、定时器触发后Main方法剩余代码是否继续运行?
会继续运行。原因:
System.Timers.Timer的Elapsed事件在线程池线程中触发,与Main方法所在主线程并行执行。- Main方法中
doprocessing(blnDelete2).Result会阻塞主线程,等待任务完成;定时器触发后仅在另一个线程执行取消逻辑,主线程仍会等待doprocessing任务结束,之后继续执行文件删除、日志记录等后续代码。
若需定时器触发后终止Main方法后续逻辑,可引入CancellationTokenSource共享标记,在取消任务后设置标记,让Main方法检测标记提前退出。
内容的提问来源于stack exchange,提问作者Ann
相关产品推荐
相关产品推荐

