如何使用计时器和不同的线程使代码平稳运行 [英] How to let the code run smoothly using timers and different threads

查看:70
本文介绍了如何使用计时器和不同的线程使代码平稳运行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于计时器间隔短并且在 Timer.Tick 事件处理程序中无法处理,因此我试图防止GUI 冻结.
我已经搜寻了一段时间,并且了解到无法从UI线程以外的任何其他线程更新UI.

I'm trying to prevent the GUI from freezing, because of a low timer interval and too much to process in the Timer.Tick event handler.
I've been googling a while and I understood that I cannot update UI from any other thread other than the UI thread.

那么,如果您在 Timer1.Tick 下使用大量控件怎么办?
当您使用WebClient通过计时器下载数据时,又不想缩短间隔时间并同时保持UI响应速度,我该如何更新Label?

So, what about if you are using lots of controls under Timer1.Tick?
How can I update a Label when the data is downloaded with WebClient with a timer, you don't want to lower the interval too much and keep the UI responsive at the same time?

当我访问UI元素,ListBox1和RichTextBox时,我收到了违反线程冲突的异常.

I receiver Cross Thread violation exceptions when I access UI elements, a ListBox1 and a RichTextBox.

用计时器和/或线程更新UI而不导致交叉威胁异常的正确方法是什么?

What is the correct way to update the UI with a timer and/or a Thread without causing cross threat exceptions?

推荐答案

除了UI线程外,您还有其他方法可以从线程中更新UI元素.
您可以使用 InvokeRequired/Invoke()模式( meh ),调用异步 BeginInvoke()方法, Post() SynchronizationContext ,也许与 AsyncOperation +

You have different ways to update UI elements from a Thread other than the UI Thread.
You can use the InvokeRequired/Invoke() pattern (meh), call the asynchronous BeginInvoke() method, Post() to the SynchronizationContext, maybe mixed with an AsyncOperation + AsyncOperationManager (solid BackGroundWorker style), use an async callback etc.

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

There's also the Progress<T> class and its IProgress<T> interface.
This class provides a quite simplified way to capture the SynchronizationContext where the class object is created and Post() back to the captured execution context.
The Progress<T> delegate created in the UI Thread is called in that context. We just need to pass the Progress<T> delegate and handle the notifications we receive.
You're downloading and handling a string, so your Progress<T> object will be a Progress(Of String): so, it will return a string to you.

计时器由执行您的代码的任务代替,并且还通过您可以指定的时间间隔(它与计时器一起在此处使用 StopWatch 可以测量实际下载时间根据指定的间隔获取并调整延迟(无论如何,这都不是精确度).

The Timer is replaced by a Task that executes your code and also delays its actions by a Interval that you can specify, as with a Timer, here using Task.Delay([Interval]) between each action. There's a StopWatch that measures the time a download actually takes and adjusts the Delay based on the Interval specified (it's not a precision thing, anyway).

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

► In the sample code, the download Task can be started and stopped using the StartDownload() and StopDownload() methods of a helper class.
The StopDownload() method is awaitable, it executes the cancellation of the current tasks and disposes of the disposable objects used.

►我已经用HttpClient替换了WebClient,使用起来还是非常简单的(至少在其基础上),它提供了支持 CancellationToken 的异步方法(尽管正在进行的下载需要一些时间取消,但在这里处理).

► I've replace WebClient with HttpClient, it's still quite simple to use (at least in its basics), it provides async methods that support a CancellationToken (though a download in progress requires some time to cancel, but it's handled here).

►单击按钮会初始化并开始定时下载,而另一次则停止下载(但您可以在窗体关闭时或需要时调用 StopDownload()方法).

► A Button click initializes and starts the timed downloads and another one stops it (but you can call the StopDownload() method when the Form closes, or, well, whenever you need to).

►此处的 Progress< T> 委托只是一个Lambda:不需要做很多事情,只需填充一个ListBox并滚动一个RichTextBox.您可以初始化帮助程序类对象(名为 MyDownloader :当然,您会选择另一个名称,这个名称很荒谬),并调用其 StartDownload()方法,在每次下载时传递Progress对象,Uri和Interval.

► The Progress<T> delegate is just a Lambda here: there's not much to do, just fill a ListBox and scroll a RichTextBox. You can initialize the helper class object (it's named MyDownloader: of course you will pick another name, this one is ridiculous) and call its StartDownload() method, passing the Progress object, the Uri and the Interval between each download.

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

这篇关于如何使用计时器和不同的线程使代码平稳运行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆