您可以使用不同的方法从 UI 线程以外的线程更新 UI 元素。
您可以使用InvokeRequired/Invoke()
图案 (meh),调用异步 BeginInvoke()
方法,Post() https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext.post to the 同步上下文 https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext,也许与一个混合异步操作 https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.asyncoperation + 异步操作管理器 https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.asyncoperationmanager(纯 BackGroundWorker 风格),使用异步回调等。
还有Progress<T> https://learn.microsoft.com/en-us/dotnet/api/system.progress-1类及其IProgress<T> https://learn.microsoft.com/en-us/dotnet/api/system.iprogress-1界面。
这个类提供了一种非常简单的方法来捕获SynchronizationContext
创建类对象的位置以及Post()
返回到捕获的执行上下文。
The Progress<T>
在 UI 线程中创建的委托在该上下文中被调用。我们只需要通过Progress<T>
委托并处理我们收到的通知。
您正在下载并处理一个字符串,因此您的Progress<T>
对象将是Progress(Of String)
:所以,它会返回一个字符串给你。
计时器被替换为任务,该任务执行您的代码,并按您可以指定的时间间隔延迟其操作,与计时器一样,此处使用任务.延迟([间隔]) https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay每个之间action。有一个跑表 https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stopwatch测量下载实际花费的时间和adjusts基于指定间隔的延迟(它不是精密的东西, 反正)。
▶ 在示例代码中,可以使用以下命令启动和停止下载任务StartDownload()
and StopDownload()
辅助类的方法。
The StopDownload()
方法是可等待的,它执行当前任务的取消并处理使用的一次性对象。
▶ 我已经用HttpClient替换了WebClient,它使用起来仍然很简单,它提供了支持异步方法CancellationToken
(尽管正在进行的下载需要某个时间取消,但在这里处理)。
▶ 单击按钮可初始化并开始定时下载,单击按钮可停止下载(但您可以调用StopDownload()
表单关闭时的方法,或者,好吧,每当您需要时)。
▶ The Progress<T>
delegate 这里只是一个 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