如何使用定时器和不同的线程使代码平稳运行



我正试图阻止GUI冻结,因为计时器间隔很短,并且Timer.Tick事件处理程序中处理的内容太多
我已经在谷歌上搜索了一段时间,我知道我不能从UI线程以外的任何其他线程更新UI。

那么,如果您在Timer1.Tick下使用了大量控件,该怎么办
如果使用带有计时器的WebClient下载数据,您不想过多地降低间隔并同时保持UI响应,我如何更新标签?

当我访问UI元素、ListBox1和RichTextBox时,我接收到跨线程冲突异常。

在不引起交叉威胁异常的情况下,用计时器和/或线程更新UI的正确方法是什么?

您有不同的方法从UI线程以外的线程更新UI元素
您可以使用InvokeRequired/Invoke()模式(meh(,将异步BeginInvoke()方法、Post()调用到SynchronizationContext,可能与AsyncOperation+AsyncOperationManager(实心BackGroundWorker风格(混合,使用异步回调等。

还有Progress<T>类及其IProgress<T>接口
该类提供了一种非常简化的方法来捕获创建类对象的SynchronizationContext,并将Post()返回到捕获的执行上下文
UI线程中创建的Progress<T>委托在该上下文中调用。我们只需要传递Progress<T>委托并处理我们收到的通知
您正在下载和处理一个字符串,因此您的Progress<T>对象将是Progress(Of String):因此,它将向您返回一个字符串。

Timer被一个Task所取代,该Task执行您的代码,并将其操作延迟一个您可以指定的Interval,就像Timer一样,这里使用Task.Delay([Interval](在每个操作之间执行。有一个StopWatch可以测量下载实际花费的时间,根据指定的间隔调整延迟(无论如何,这不是精度的问题(。


▶在示例代码中,下载任务可以使用helper类的StartDownload()StopDownload()方法启动和停止
StopDownload()方法是不可用的,它执行当前任务的取消并处理所使用的一次性对象。

▶我已经用HttpClient取代了WebClient,它仍然很容易使用,它提供了支持CancellationToken的异步方法(尽管正在进行的下载需要一些时间才能取消,但它在这里处理(。

▶单击一个按钮初始化并启动定时下载,另一个按钮停止下载(但您可以在Form关闭时调用StopDownload()方法,或者在需要时调用(。

Progress<T>委托在这里只是一个Lambda:没有什么可做的,只需填充一个ListBox并滚动一个RichTextBox
您可以初始化helper类对象(其名称为MyDownloader:当然您会选择另一个名称,这个名称很荒谬(并调用其StartDownload()方法,在每次下载之间传递Progress<T>对象、UriInterval

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

您的listbox和richtextbox访问必须在UI线程上运行。最简单的方法就是这样。

Me.Invoke(Sub()
ListBox1.Items.Clear()
ListBox1.Items.AddRange(Split(clientdecode, vbLf))
RichTextBox1.SelectionStart() = RichTextBox1.TextLength
RichTextBox1.ScrollToCaret()
End Sub)

相关内容

  • 没有找到相关文章

最新更新