如何让代码运行顺畅的使用定时器和不同的线程
How to let the code run smoothly using timers and different threads
我正试图阻止 GUI 冻结,因为计时器间隔很短并且 Timer.Tick
事件中有太多要处理的东西处理程序。
我在谷歌上搜索了一段时间,我知道我无法从 UI 线程以外的任何其他线程更新 UI。
那么,如果您在 Timer1.Tick
下使用大量控件怎么办?
当使用带有计时器的 WebClient 下载数据时,我如何更新标签,您不想降低间隔太多并同时保持 UI 响应?
当我访问 UI 个元素、一个 ListBox1 和一个 RichTextBox 时,我收到跨线程冲突异常。
在不导致交叉威胁异常的情况下,使用计时器 and/or 更新 UI 线程的正确方法是什么?
您的列表框和 richtextbox 访问必须 运行 在 UI 线程上。最简单的方法是这样的。
Me.Invoke(Sub()
ListBox1.Items.Clear()
ListBox1.Items.AddRange(Split(clientdecode, vbLf))
RichTextBox1.SelectionStart() = RichTextBox1.TextLength
RichTextBox1.ScrollToCaret()
End Sub)
您可以通过不同的方式从 UI 线程以外的线程更新 UI 元素。
可以使用InvokeRequired/Invoke()
模式(meh),调用异步BeginInvoke()
方法,Post()
to the SynchronizationContext, maybe mixed with an AsyncOperation + AsyncOperationManager( solid BackGroundWorker 样式),使用异步回调等
还有 Progress<T>
class and its IProgress<T>
界面。
class 提供了一种非常简单的方法来捕获创建 class 对象的 SynchronizationContext
并将 Post()
返回到捕获的执行上下文。
在该上下文中调用在 UI 线程中创建的 Progress<T>
委托。我们只需要传递 Progress<T>
委托并处理我们收到的通知。
您正在下载和处理一个字符串,因此您的 Progress<T>
对象将是一个 Progress(Of String)
:因此,它将 return 一个字符串给您。
Timer 被一个 Task 取代,Task 执行您的代码,并通过您可以指定的 Interval 延迟其操作,与 Timer 一样,这里使用 Task.Delay([Interval]) between each action. There's a StopWatch 测量下载实际花费的时间和根据指定的间隔调整延迟(无论如何,这不是精度)。
▶ 在示例代码中,可以使用 StartDownload()
和 StopDownload()
[=71 来启动和停止下载任务=] 助手的方法 class.
StopDownload()
方法是可等待的,它执行当前任务的取消并处理使用的一次性对象。
▶ 我已经用 HttpClient 替换了 WebClient,它使用起来仍然很简单,它提供了支持 CancellationToken
的异步方法(尽管正在进行的下载需要 一些时间取消,这里处理。
▶ 单击按钮初始化并开始定时下载,然后单击另一个按钮停止下载(但您可以在表单关闭时调用 StopDownload()
方法,或者,好吧,只要您需要)。
▶ Progress<T>
委托在这里只是一个 Lambda:没什么可做的,只需填充一个 ListBox 并滚动一个 RichTextBox。
你可以初始化helperclass对象(它的名字叫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
帮手class:
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
我正试图阻止 GUI 冻结,因为计时器间隔很短并且 Timer.Tick
事件中有太多要处理的东西处理程序。
我在谷歌上搜索了一段时间,我知道我无法从 UI 线程以外的任何其他线程更新 UI。
那么,如果您在 Timer1.Tick
下使用大量控件怎么办?
当使用带有计时器的 WebClient 下载数据时,我如何更新标签,您不想降低间隔太多并同时保持 UI 响应?
当我访问 UI 个元素、一个 ListBox1 和一个 RichTextBox 时,我收到跨线程冲突异常。
在不导致交叉威胁异常的情况下,使用计时器 and/or 更新 UI 线程的正确方法是什么?
您的列表框和 richtextbox 访问必须 运行 在 UI 线程上。最简单的方法是这样的。
Me.Invoke(Sub()
ListBox1.Items.Clear()
ListBox1.Items.AddRange(Split(clientdecode, vbLf))
RichTextBox1.SelectionStart() = RichTextBox1.TextLength
RichTextBox1.ScrollToCaret()
End Sub)
您可以通过不同的方式从 UI 线程以外的线程更新 UI 元素。
可以使用InvokeRequired/Invoke()
模式(meh),调用异步BeginInvoke()
方法,Post()
to the SynchronizationContext, maybe mixed with an AsyncOperation + AsyncOperationManager( solid BackGroundWorker 样式),使用异步回调等
还有 Progress<T>
class and its IProgress<T>
界面。
class 提供了一种非常简单的方法来捕获创建 class 对象的 SynchronizationContext
并将 Post()
返回到捕获的执行上下文。
在该上下文中调用在 UI 线程中创建的 Progress<T>
委托。我们只需要传递 Progress<T>
委托并处理我们收到的通知。
您正在下载和处理一个字符串,因此您的 Progress<T>
对象将是一个 Progress(Of String)
:因此,它将 return 一个字符串给您。
Timer 被一个 Task 取代,Task 执行您的代码,并通过您可以指定的 Interval 延迟其操作,与 Timer 一样,这里使用 Task.Delay([Interval]) between each action. There's a StopWatch 测量下载实际花费的时间和根据指定的间隔调整延迟(无论如何,这不是精度)。
▶ 在示例代码中,可以使用 StartDownload()
和 StopDownload()
[=71 来启动和停止下载任务=] 助手的方法 class.
StopDownload()
方法是可等待的,它执行当前任务的取消并处理使用的一次性对象。
▶ 我已经用 HttpClient 替换了 WebClient,它使用起来仍然很简单,它提供了支持 CancellationToken
的异步方法(尽管正在进行的下载需要 一些时间取消,这里处理。
▶ 单击按钮初始化并开始定时下载,然后单击另一个按钮停止下载(但您可以在表单关闭时调用 StopDownload()
方法,或者,好吧,只要您需要)。
▶ Progress<T>
委托在这里只是一个 Lambda:没什么可做的,只需填充一个 ListBox 并滚动一个 RichTextBox。
你可以初始化helperclass对象(它的名字叫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
帮手class:
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