投稿者 mayopee  (社会人) 投稿日時 2020/5/28 14:52:33
環境:VB2019,.Net4.7.2,WinForm

エクスプローラーのようなツールを作成しています。左ペインにフォルダTreeViewがあり、
TreeNode変更で選択フォルダ内のファイルに対して様々な処理を行います。
ファイル列挙時に途中キャンセルを実装したいのですが、希望動作になりません。
希望動作は「列挙途中でキャンセル指示がでれば列挙を中止して、新しい列挙を開始する」
としたいのです。

実際とは異なりますが、再現可能なコードを掲載するので、試して戴けないでしょうか?
EscキーでTaskキャンセルとしています。

↓のコードで 「1」=>「2」=>「1」=>「2」=>「Task Cancel」とNodeを素早くクリックした時、
以下の【NGパターン】となります。

【希望動作】
1の列挙開始=>1のキャンセル=>2の列挙開始=>2のキャンセル=>1の列挙開始=>.....

【NGパターン】
1の列挙開始=>2の列挙開始=>1のキャンセル=>2の列挙再開=>1の列挙開始=>.....

続けて、「ListBox Clear」を押して同じ操作をすると、2回目は希望動作になります。
メモリにキャッシュされる感じで動作も軽くなります。理由はわかりません。

又、Escキーの押下で以下のようにすると希望動作にはなります。
「1」=>「Escキー」=>「2」=>「Escキー」=>「1」=>「Escキー」=>「2」=>「Escキー」

TreeNodeの変更で上記【希望動作】とするには、どうすれば良いでしょうか?
TreeNodeの変更はTreeViewのAfterSelectをトリガとしています。

Imports System.Threading
Public Class Form1
    Private _TV As New TreeView With {.Dock = DockStyle.Fill}
    Private _LB As New ListBox With {.Dock = DockStyle.Fill}
    Private _cts As CancellationTokenSource
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim sp As New SplitContainer With {.Dock = DockStyle.Fill}
        Dim root As New TreeNode("Task Cancel")
        Dim tn1 As New TreeNode("1")
        Dim tn2 As New TreeNode("2")
        Dim tn3 As New TreeNode("ListBox Clear")
        root.Nodes.AddRange({tn1, tn2, tn3})
        _TV.Nodes.Add(root)
        sp.Panel1.Controls.Add(_TV)
        sp.Panel2.Controls.Add(_LB)
        Controls.Add(sp)
        root.Expand()
        _TV.SelectedNode = root
        AddHandler _TV.AfterSelect, AddressOf TreeView_AfterSelect
        KeyPreview = True
        AddHandler Me.KeyDown, Sub(o, k)
                                   If k.KeyCode = Keys.Escape Then
                                       _cts?.Cancel()
                                   End If
                               End Sub
    End Sub
    Private Async Sub TreeView_AfterSelect(sender As Object, e As TreeViewEventArgs)
        Try
            SendKeys.SendWait("{ESC}")
            'Await Task.Delay(1000) 
            _cts = New CancellationTokenSource()
            Select Case e.Node.Text
                Case "1"
                    Await Task.Run(Sub()
                                       Try
                                           For Each item In Enumerable.Range(1, 10000)
                                               _cts?.Token.ThrowIfCancellationRequested()
                                               SetText($"Node1({item})")
                                           Next
                                       Catch ex As OperationCanceledException
                                           Invoke(Sub() _LB.Items.Add("1 キャンセル ★★★★★★★★★★★★★★★★★★★★★★★★"))
                                       End Try
                                   End Sub)
                Case "2"
                    Await Task.Run(Sub()
                                       Try
                                           For Each item In Enumerable.Range(1, 10000)
                                               _cts?.Token.ThrowIfCancellationRequested()
                                               SetText($"Node2({item})")
                                           Next
                                       Catch ex As OperationCanceledException
                                           Invoke(Sub() _LB.Items.Add("2 キャンセル ★★★★★★★★★★★★★★★★★★★★★★★★"))
                                       End Try
                                   End Sub)
                Case "ListBox Clear"
                    _LB.Items.Clear()
            End Select
        Finally
            _cts?.Dispose()
            _cts = Nothing
        End Try
    End Sub
    Private Sub SetText(s As String)
        If InvokeRequired Then
            Invoke(Sub() SetText(s))
        Else
            _LB.Items.Add(s)
        End If
    End Sub
End Class