您有不同的方法从 UI 线程以外的线程更新 UI 元素。
您可以使用InvokeRequired/Invoke()
模式 ( meh ),调用异步 BeginInvoke()
方法,Post()
调用SynchronizationContext,可能与AsyncOperation + AsyncOperationManager (solid BackGroundWorker 样式)混合,使用异步回调等。
还有Progress<T>
类及其IProgress<T>
接口。
此类提供了一种非常简化的方法来捕获SynchronizationContext
创建类对象的位置并Post()
返回到捕获的执行上下文。在该上下文中调用在 UI 线程中创建
的委托。Progress<T>
我们只需要传递Progress<T>
委托并处理我们收到的通知。
你正在下载和处理一个字符串,所以你的Progress<T>
对象将是一个Progress(Of String)
: 所以,它会返回一个字符串给你。
Timer 被一个 Task 替换,它执行您的代码,并通过您可以指定的 Interval 延迟其操作,就像 Timer 一样,在每个操作之间使用Task.Delay([Interval])。有一个秒表可以测量下载实际花费的时间,并根据指定的间隔调整延迟(无论如何,这不是一个精确的东西)。
▶ 在示例代码中,可以使用辅助类的StartDownload()
和方法来启动和停止下载任务。
该方法是等待的,它执行当前任务的取消并处理使用的一次性对象。StopDownload()
StopDownload()
▶ 我已经用HttpClient替换了WebClient,它使用起来还是很简单的,它提供了支持a的异步方法CancellationToken
(虽然正在进行的下载需要一些时间来取消,但是这里处理了)。
▶ 一个按钮单击初始化并开始定时下载,另一个按钮停止它(但您可以StopDownload()
在窗体关闭时调用该方法,或者,只要您需要)。
▶Progress<T>
委托在这里只是一个 Lambda:没什么可做的,只需填充一个 ListBox 并滚动一个 RichTextBox。
你可以初始化辅助类对象(它的名字MyDownloader
是:当然你会选择另一个名字,这个名字很可笑)并调用它的StartDownload()
方法,传递Progress<T>
对象,每次下载之间的Uri
和。Interval
Private downloader As MyDownloader = Nothing
Private Sub btnStartDownload_Click(sender As Object, e As EventArgs) Handles btnStartDownload.Click
Dim progress = New Progress(Of String)(
Sub(data)
' We're on the UI Thread here
ListBox1.Items.Clear()
ListBox1.Items.AddRange(Split(data, vbLf))
RichTextBox1.SelectionStart = RichTextBox1.TextLength
End Sub)
Dim url As Uri = New Uri("https://SomeAddress.com")
downloader = New MyDownloader()
' Download from url every 1 second and report back to the progress delegate
downloader.StartDownload(progress, url, 1)
Private Async Sub btnStopDownload_Click(sender As Object, e As EventArgs) Handles btnStopDownload.Click
Await downloader.StopDownload()
End Sub
助手类:
Imports System.Diagnostics
Imports System.Net
Imports System.Net.Http
Imports System.Text.RegularExpressions
Public Class MyDownloader
Implements IDisposable
Private Shared client As New HttpClient()
Private cts As CancellationTokenSource = Nothing
Private interval As Integer = 0
Private disposed As Boolean
Public Sub StartDownload(progress As IProgress(Of String), url As Uri, intervalSeconds As Integer)
interval = intervalSeconds * 1000
Task.Run(Function() DownloadAsync(progress, url, cts.Token))
End Sub
Private Async Function DownloadAsync(progress As IProgress(Of String), url As Uri, token As CancellationToken) As Task
token.ThrowIfCancellationRequested()
Dim responseData As String = String.Empty
Dim pattern As String = "<(?:[^>=]|='[^']*'|=""[^""]*""|=[^'""][^\s>]*)*>"
Dim downloadTimeWatch As Stopwatch = New Stopwatch()
downloadTimeWatch.Start()
Do
Try
Using response = Await client.GetAsync(url, HttpCompletionOption.ResponseContentRead, token)
responseData = Await response.Content.ReadAsStringAsync()
responseData = WebUtility.HtmlDecode(Regex.Replace(responseData, pattern, ""))
End Using
progress.Report(responseData)
Dim delay = interval - CInt(downloadTimeWatch.ElapsedMilliseconds)
Await Task.Delay(If(delay <= 0, 10, delay), token)
downloadTimeWatch.Restart()
Catch tcEx As TaskCanceledException
' Don't care - catch a cancellation request
Debug.Print(tcEx.Message)
Catch wEx As WebException
' Internet connection failed? Internal server error? See what to do
Debug.Print(wEx.Message)
End Try
Loop
End Function
Public Async Function StopDownload() As Task
Try
cts.Cancel()
client?.CancelPendingRequests()
Await Task.Delay(interval)
Finally
client?.Dispose()
cts?.Dispose()
End Try
End Function
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposed AndAlso disposing Then
client?.Dispose()
client = Nothing
End If
disposed = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
End Sub
End Class