在我的应用程序中,我调用了一个更新软件的过程-该过程存储在其自己的类中。即使您有Async / Wait,并且debug.print出于某种原因也在frmUpdate.ReportProgress()
内返回一条消息,但更新表单中的进度条未更新...
班级
Namespace software
Public Class updater
Public Async Function UpdateSoftware(ByVal url As String, ByVal downloadFolder As String) As Tasks.Task(Of Boolean)
Dim progressIndicator = New Progress(Of Integer)(AddressOf ReportProgress)
Await SetProgressBar(progressIndicator, 100)
Return True
End Function
Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
myProgress.Report(counter)
End Function
Private Function ReportProgress(ByVal count As Integer)
frmUpdate.ReportProgress(count)
End Function
End Class
End Namespace
下面表格的代码
Public Class frmUpdate
Private Async Sub btnUpdate_Click(sender As System.Object, e As System.EventArgs) Handles btnUpdate.Click
updater.UpdateSoftware(url, downloadFolder)
End Function
Public Function ReportProgress(ByVal myInt As Integer)
ProgressBar1.Value = myInt
Debug.Print(ProgressBar1.Value)
End Function
End Class
答案 0 :(得分:2)
Async
和Await
本质上不会使事物成为多线程的。有充分的理由。并非所有异步任务都是多线程的。例如,如果要让某些任务等到用户单击按钮,则不需要该任务位于其自己的线程上。如果有一个单独的线程在等待按钮被单击时仅坐在那里循环,那将是非常浪费的。在这种情况下,您只需使按钮的click事件发出信号即可继续执行任务/完成任务。然后,任务可以简单地阻止执行,直到接收到该信号为止。由于在所有异步任务中都设置多线程是很浪费的,所以在新线程中启动任务是一个单独的可选操作。
仅将Async
关键字添加到方法签名中并不能使您的方法在单独的线程上运行。实际上,从技术上讲,它甚至不会使您的方法异步。仅当您在某个方法内部调用Await
时,它才会是异步的。因此,您的方法之间没有明显的区别:
Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
myProgress.Report(counter)
End Function
这:
Private Sub SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer)
myProgress.Report(counter)
End Sub
这两个方法在被调用时都会立即执行,并且两个方法都以调用它们的方法阻止执行,直到它们完成为止。如果myProgress
(无论是哪种方式)提供了Report
方法的异步版本(即返回Task
的等效方法),则您要调用它并等待它: / p>
Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
Await myProgress.ReportAsync(counter)
End Function
如果不存在这样的异步替代方法,则可以通过在其自己的线程中启动它来强制使其异步:
Private Async Function SetProgressBar(ByVal myProgress As IProgress(Of Integer), counter As Integer) As Tasks.Task
Await Task.Run(Sub() myProgress.ReportAsync(counter))
End Function
但是,在这种情况下,根据方法的名称,我可以肯定地说,它根本不需要异步。真正的问题是,无论您在UpdateSoftware
中从事的长期工作是什么,Await
都没有完成,因此它本来应该阻止的。但是,由于您没有显示该代码,因此很难给出一个很好的例子。考虑这样的事情:
Public Class updater
Public Async Function UpdateSoftware(ByVal url As String, ByVal downloadFolder As String) As Tasks.Task(Of Boolean)
Await Task.Run(AddressOf LongRunningWork)
Return True
End Function
Private Sub LongRunningWork()
' Do something that takes a while
For i As Integer = 1 to 100
ReportProgress(i)
Thread.Sleep(100)
Next
End Sub
Private Sub ReportProgress(count As Integer)
frmUpdate.BeginInvoke(Sub() frmUpdate.ReportProgress(count))
End Function
End Class
请注意,在ReportProgress
方法中,我让它在表单上调用BeginInvoke
(尽管Invoke
也可以工作)以使其在UI线程上执行该表单的方法比任务线程上的要多。这始终很重要。每当您更新UI时,总是需要调用UI线程来进行更新,否则将获得跨线程异常。
此外,您也不应该在事件处理程序中使用Await
,而应该这样做。从技术上讲,它无论哪种方式都可以工作,但是如果您开始添加异常处理,您会很快发现它会带来很大的不同:
Private Async Sub btnUpdate_Click(sender As System.Object, e As System.EventArgs) Handles btnUpdate.Click
Await updater.UpdateSoftware(url, downloadFolder)
End Function
有关异步/等待的更多信息(Microsoft称之为基于任务的异步模式,简称TAP),请参见documentation。 TAP是一个非常强大的工具。它使异步代码非常易于阅读和理解。但是,尽管表面上看起来很简单,但是仍然需要对基本概念有一个很好的理解才能正确使用它。如果您对此不确定,则可以尝试使用BackgroundWorker
组件,就像我在上一个问题的my answer中所建议的那样,因为它的魔力稍差一些,可能更容易理解哪里发生了什么,为什么发生了。