How to let the code run smoothly using timers and different threads

后端 未结 2 1626
暖寄归人
暖寄归人 2021-01-29 05:12

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 goo

相关标签:
2条回答
  • 2021-01-29 05:52

    Your listbox and richtextbox accesses must run on the UI thread. The easiest way to do it is like this.

                                         Me.Invoke(Sub()
                                                       ListBox1.Items.Clear()
                                                       ListBox1.Items.AddRange(Split(clientdecode, vbLf))
                                                       RichTextBox1.SelectionStart() = RichTextBox1.TextLength
                                                       RichTextBox1.ScrollToCaret()
                                                   End Sub)
    
    0 讨论(0)
  • 2021-01-29 06:10

    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.

    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> object and handle the notifications we receive through the Action<T> delegate.
    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.

    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).


    ► 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.

    ► 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).

    ► 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).

    ► 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
    

    The helper class:

    Imports System.Diagnostics
    Imports System.Net
    Imports System.Net.Http
    Imports System.Text.RegularExpressions
    
    Public Class MyDownloader
        Private Shared ReadOnly client As New HttpClient()
        Private ReadOnly cts As CancellationTokenSource = New CancellationTokenSource()
        Private interval As Integer = 0
    
        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
            Dim responseData As String = String.Empty
            Dim pattern As String = "<(?:[^>=]|='[^']*'|=""[^""]*""|=[^'""][^\s>]*)*>"
            Dim downloadTimeWatch As Stopwatch = New Stopwatch()
            downloadTimeWatch.Start()
            Do
                If cts.IsCancellationRequested Then Return
                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
    End Class
    
    0 讨论(0)
提交回复
热议问题