You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

报错截图

报错截图1
报错截图2


问题排查与解答

一、Await Task.WhenAll(jobCancelledList)异常排查

  1. 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)
    
  2. 异步事件处理器声明问题
    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
    
  3. 单个任务取消异常影响全局
    需为每个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 14:49:51