开发者

Trying to use Threading.Tasks.Task to kick off a background search while user types, but after they pause

开发者 https://www.devze.com 2023-04-10 03:38 出处:网络
I\'ve searched but I can\'t find the solution I\'m looking for. I specifically want to use Threading.Tasks.Task for this project, just to understand it better.

I've searched but I can't find the solution I'm looking for.

I specifically want to use Threading.Tasks.Task for this project, just to understand it better.

I have a lengthy XML file that I want to search based on text that a user types in. Because it's lengthy, I want to wait for the user to stop typing for 250ms or so before I actually start searching in the background. I tried to kick off my Task, having it sleep for 250ms, then check if my CancelTokenSource had canceled because another character was typed. I'm not seeing a cancellation, though, so I end up seeing my results flash several times as the queued up search tasks complete, one after the other, after I finish typing.

My code has turned into a terrible mess and I need to start over,开发者_StackOverflow中文版 but was hoping someone could point me in the right direction.


Start with a thread-safe property that determines when the search should begin. Initialise it to Date.MaxValue to prevent it running before it's asked to.

Private Property SearchTriggerTime As Date
    Get
        SyncLock SearchTriggerTimeLock
            Return _SearchTriggerTime
        End SyncLock
    End Get
    Set(value As Date)
        SyncLock SearchTriggerTimeLock
            _SearchTriggerTime = value
        End SyncLock
    End Set
End Property
Private _SearchTriggerTime As Date = Date.MaxValue
Private ReadOnly SearchTriggerTimeLock As New Object

When the search text box text changes, set the timer to when you want to start searching. As the user types quickly, the search timer will be reset before it triggers. In the code below, if the user clears the text box, the timer is set to never trigger, ie. do not search.

Private Const SearchDelay As Integer = 250

Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
    If TextBox1.Text <> "" Then
        SearchTriggerTime = Date.Now.AddMilliseconds(SearchDelay)
    Else
        SearchTriggerTime = Date.MaxValue
    End If
End Sub

Start a thread to perform the searching when the form loads.

Public Sub Form_Load(sender As Object, e As EventArgs) Handles Me.Load
    With New Thread(AddressOf SearchThread)
        .Start()
    End With
End Sub

This thread passes through three states. The first state is waiting for the trigger time to start the search. It checks the trigger time every 50ms. The second state is performing the search. During the search, it checks if the form closes or if the user types more, and abandons the search in those cases. In the third state, if the search completes normally, the form's original thread is asked to display the results. If you need to change a control, always do so on the form's thread by using Form.Invoke(Delegate).

Private Sub SearchThread()
    Do Until IsDisposed
        ' Wait for the user to stop typing.
        Do Until IsDisposed OrElse SearchTriggerTime <= Date.Now
            Thread.Sleep(50)
        Loop
        ' Search until the form is disposed, the user types more, or the search is complete.
        ' TODO: Initialise the search variables.
        Dim SearchComplete As Boolean = False
        Do Until IsDisposed OrElse SearchTriggerTime > Date.Now OrElse SearchComplete
            ' TODO: Insert search code here.
            If condition Then SearchComplete = True
        Loop
        ' Reset the search timer.
        SearchTriggerTime = Date.MaxValue
        ' Only display results if the search was completed.
        If SearchComplete Then Invoke(New Action(AddressOf DisplaySearchResults))
    Loop
End Sub

Lastly, create a method to display your search results. This will be run in the form's thread to prevent invalid cross-thread operations.

Private Sub DisplaySearchResults()
    ' TODO: Display search results.
End Sub


I managed to get it to work and it actually looks fairly clean (I think). I'm using 'Static' variables so that I can keep all of the code within this method but still have my CancellationTokenSource available on subsequent calls. I'm pretty pleased with the result, but still welcome comments and notes for improvement. The actual search is actually happening on the UI thread (I think) but I'm doing that because it's easy to populate a treeview while I find valid nodes in my XML. My next refactor will be to do the search on a background thread (rather than just the 250ms wait) and use a TaskScheduler to post UI updates.

    Private Sub txtQuickSearch_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtQuickSearch.TextChanged
    Static scCancelTokenSource As Threading.CancellationTokenSource
    Static scCancelToken As Threading.CancellationToken

    If txtQuickSearch.Text.Length >= 3 Then
        If scCancelTokenSource IsNot Nothing Then
            scCancelTokenSource.Cancel()
            scCancelTokenSource = Nothing
        End If

        scCancelTokenSource = New Threading.CancellationTokenSource
        scCancelToken = scCancelTokenSource.Token

        Dim lfSearch = Sub()
                           Dim ltToken As Threading.CancellationToken = scCancelToken
                           Threading.Thread.Sleep(250)
                           If Not ltToken.IsCancellationRequested Then
                               Me.Invoke(Sub() DoQuickSearch(txtQuickSearch.Text))
                           End If
                       End Sub

        Threading.Tasks.Task.Factory.StartNew(lfSearch, scCancelToken)
    End If
End Sub
0

精彩评论

暂无评论...
验证码 换一张
取 消