首页 > 解决方案 > 创建一个等待延迟替代 Application.DoEvents

问题描述

多年来,我使用以下方法在我的软件中造成延迟:

Wait(10000)

Sub Wait(milliseconds)
    <here I get the current time>
    Do
         <here I loop until the current time passed in seconds and then exit this loop>
         Application.DoEvents()
    Loop
End Sub

问题是,这会占用大量 CPU。我试过Thread.Sleep(1000)了,但这会在我的应用程序执行时冻结我的应用程序!
我尝试使用计时器,但我仍然需要一个不会冻结但行为类似于 Application.DoEvents() 的循环。这似乎是不可能的。

我的目标是这样做:

label1.text = "ok about to start"
Wait(5000)        
' the following line CAN NOT run until after 5 seconds. 
label1.text = "DONE"

标签: vb.netmultithreadingwinformstimerdoevents

解决方案


延迟后如何执行代码。

有不同的方法可以在延迟后执行代码或异步执行代码,无论是否延迟。在这里,我正在考虑一个 Timer 和 Async/Await 模式的简单实现。

Application.DoEvent()在任何情况下都应避免调用循环。


► 使用Timer以一种或多种方法延迟单个指令或代码的执行:

您不能等待 Timer,但您可以使用创建 Timer 并在 Timer 引发其事件时执行 Action 的方法,指示指定的 Interval 已过。

后面的方法接受一个delay值和一个Action委托作为参数。
Delay 用于设置 Timer 的 Interval,Action 表示 Timer Ticks 时将执行的代码(我在这里使用System.Windows.Forms.Timer,因为您似乎指的是 WinForms 应用程序)。

Private waitTimer As New System.Windows.Forms.Timer()
Private TimerTickHandler As EventHandler

Private Sub Wait(delay As Integer, action As Action)
    waitTimer.Interval = delay
    TimerTickHandler = New EventHandler(
        Sub()
            action.Invoke()
            waitTimer.Stop()
            RemoveHandler waitTimer.Tick, TimerTickHandler
        End Sub)
    AddHandler waitTimer.Tick, TimerTickHandler
    waitTimer.Start()
End Sub

当我们需要延迟执行代码时,我们可以调用这个方法。

Action 可以是一个简单的指令:在这种情况下,Text oflabel1将在 5 秒后设置为“Done”,而 UI Thread 继续其操作:

label1.text = "About to Start..."
Wait(5000, Sub() Me.label1.Text = "Done")

Action 也可以是一个方法:

Private Sub SetControlText(control As Control, controlText As String)
    control.Text = controlText
End Sub

' Elsewhere
Wait(5000, Sub() SetControlText(Me.label1, "Done"))

当然,该SetControlText()方法可以执行更复杂的代码,并且可以选择设置 Timer 本身,调用Wait().

► 使用异步/等待模式

异步方法提供了一种方便的方法来完成可能 长时间运行的工作,而不会阻塞调用者的线程。异步方法的调用者 无需等待异步 方法完成即可恢复其工作。

简单来说,将Async 修饰符添加到方法中,允许使用Await 运算符等待异步过程终止,然后再执行后面的代码,而当前线程可以自由地继续其处理。


▬ 请注意,Async修饰符始终应用于Function()返回 aTask或 a 的 a Task(Of something)(可以方法可以返回的任何值/引用)。
它仅适用于( ) 方法表示aSub()时的a 。毫无例外地记住和应用这一点非常重要(除非您非常清楚其中的含义)。▬</p> SubvoidEvent Handler

阅读有关此的文档(在上一个链接中)和这些:

Async 和 Await
不要阻塞异步代码


一个简单的Async方法可以用来延迟一个动作的执行:

Private Async Function Wait(delay As Integer, action As Action) As Task
    Await Task.Delay(delay)
    action?.Invoke()
End Function

这与 Timer 功能类似,并且以类似的方式运行。不同之处在于,您可以使用Await此方法执行异步运行的代码,并在方法返回后等待其完成运行其他代码。调用线程(此处为 UI 线程)将在Wait()等待方法时继续其操作。

假设在Button.Click处理程序中,我们要执行一个 Action(一个不返回值的方法)或一个 Function(一个返回值的方法),并且后面的代码应该只在这个 Action 或 Function 返回后执行:

Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
    Await Wait(5000, New Action(Sub() Me.label1.Text = "Done"))
    Await Wait(5000, Nothing)
    ' (...)
    ' ... More code here. It will execute after the last Await completes
End Sub

这里我们指令等待5秒,然后给一个Label的Text设置一个值,再等5秒,什么都不做,然后执行下面的代码

如果我们不需要执行一个动作,我们可以简单地使用Task.Delay()

Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
    label1.text = "About to Start..."
    Await Task.Delay(5000)
    label1.Text = "Done"
    ' (...)
    ' ... More code here. It will execute after the last Await completes
End Sub

我们还可以使用 Async/Await 等待在ThreadPool Thread中运行的 Task 完成,调用Task.Run()来执行 Lambda 表达式中的代码:(
只是一个示例,我们不应该使用 ThreadPool Thread一个简单的任务)

Private Async Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
    label1.text = "About to Start..."
    Await Task.Run(Async Function()
                       Await Task.Delay(5000)
                       BeginInvoke(New Action(Sub() Me.label1.Text = "Done"))
                   End Function)
    ' (...)
    ' ... More code here. It will execute after the last Await completes
End Sub

另请参阅Task.ContinueWith()方法:

创建一个在目标任务完成时异步执行的延续。


推荐阅读