非同期の無限ループ処理の終了とThread.Abort()の使用可否

huahi11115 340 評価のポイント
2024-09-26T11:21:08.5466667+00:00

VB.net,.Net Framework 4.7.1を使用した開発をしています。

あるメソッド p1 を新規スレッドとして開始するのに、次の様な方法を使っています。

Dim th1 As Thread

th1 = New Thread(AddressOf p1)

th1.Start()

th1の終了は、この様にしています。

th1.Abort()

th1.Join()

ここで、Microsoftの.Net Frameworkのドキュメントを読むと、th1.Abort()は色々な意味で危険なのでCancellationTokenを使用しなさいと解説されているので、この方法について調べてみたところ、

While(1)

If  cancelToken.IsCancellationRequested = true then

’終了処理

End if

End While

↑この様な、ポーリングでCancellationTokenSource.Cancelをモニターする方法しか見つかりません。

開発の現状を説明すると、歴史的に古くから普及しているC++のネイティブライブラリーの、ある関数をVB.netから使用したいのですが、条件により関数からの処理の戻りが非常に遅い場合があり(数十秒から数十分)、ユーザーの操作によって処理を中断したいと思っています。

ここまで処理に時間の掛かる関数は、無限ループとみなして良いと思います。

危険だとされるth1.Abort()を使って、過去に問題が発生したことはありませんが、安全であるとされるCancellationTokenを使おうとしても、th1.Abort()の様にバッサリとスレッドが終了できるわけではなく、ポーリングが必要であれば、代替策とはなり得ません。

質問したいことは、

(1) 現状通り、C++のネイティブライブラリー内の関数に対してAbort()で問題が発生していなければ、このまま開発を進めるべきなのか。

(2) ポーリングをせずにCancellationTokenによるスレッドの強制終了はできるのか。

御回答をお待ちしております。不明なところがあれば質問して下さい。

.NET
.NET
.NET ソフトウェア フレームワークに基づく Microsoft テクノロジ。
66 件の質問
VB
VB
.NET Framework 上に実装される、Microsoft によって開発されたオブジェクト指向プログラミング言語。 旧称 Visual Basic .NET。
10 件の質問
{count} 件の投票

承認済みの回答
  1. gekka 9,666 評価のポイント MVP
    2024-09-26T16:18:57.9466667+00:00

    >1

    AbortではThreadAbortExceptionで適切な終了処理を行ってリークしないようにすることと、要求タイミングによってはキャッチができないことに対処ができるのであれば、Abortしてもかまわないです。 ですが、正しく処理できるかは、ネイティブ側をどのように呼んでいるか、ネイティブ側で何をやっているのかに依存するので、絶対的に正しいとは言えません。
    ですから一般論として正しいと保証できないのでAbortは使うなとしか言えなくなります。
    ゆえに、Abortしても問題がないかを判断できるのはあなただけであり、保証しなければならないのもあなたです。 ライブラリの作成もあなたが行っているのであれば、途中キャンセル可能なようにライブラリを改修するのが最善でしょう。

    >2

    CancellationTokenは高機能なフラグなので、IsCancellationRequestedをチェックするだけならBooleanをフラグとすることとたいして違いはないです。
    その使い方だけならスレッド内の進捗ごとにフラグチェックして処理を抜けるだけになります。

    そうではなく、CancellationTokenをイベントの発生源として扱うことで、キャンセル要求時にネイティブ側にたいしてにキャンセル処理を実行させる使い方ができます。

    Imports System
    Imports System.Threading
    
    Module Module1
    
        Sub Main()
            Test1()
            Console.WriteLine("-------------")
            Test2()
            Console.WriteLine("-------------")
            Test3()
            Console.WriteLine("終了")
            Console.ReadKey()
        End Sub
        Private Sub WriteLine(msg As String)
            Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " " + msg)
        End Sub
    
    
        '---------------------------------------------------------------------
        Private Sub Test1()
            WriteLine("Test1")
            Dim th1 As System.Threading.Thread = New Thread(AddressOf p1)
            th1.Start(Nothing)
    
            System.Threading.Thread.Sleep(1000)
            WriteLine("Request Abort")
            th1.Abort()
            th1.Join()
    
            WriteLine(th1.ThreadState.ToString())
        End Sub
    
        Private Sub p1(ByVal o As Object)
            WriteLine("CppFunc1 Start")
            Try
                CppFunc1()
            Catch ex As System.Threading.ThreadAbortException
                WriteLine("CppFunc1 Abort")
                'Abortされたら安全に終了させる処理を実行しなければならない
                'しなかったらメモリリークなどが起きる可能性がある
            Finally
                WriteLine("CppFunc1 Finally")
            End Try
            WriteLine("CppFunc1 End") 'Abortされると実行されない
        End Sub
    
        Private Sub CppFunc1()
            System.Threading.Thread.Sleep(1000000) '途中解除する方法がないのでAbortするしかない
        End Sub
    
        '---------------------------------------------------------------------
        Private Sub Test2()
            WriteLine("Test2")
            Dim th1 As System.Threading.Thread = New Thread(AddressOf p2)
            th1.Start(System.Threading.CancellationToken.None)
    
            System.Threading.Thread.Sleep(1000)
    
            WriteLine("Request Abort")
            th1.Abort() 'キャンセルせずにAbortしてもすぐには返ってこない
            th1.Join()
    
            WriteLine(th1.ThreadState.ToString())
        End Sub
        '---------------------------------------------------------------------
        Private Sub Test3()
            WriteLine("Test3")
            'CancellationTokenをつかう
            Dim tcs As New System.Threading.CancellationTokenSource()
    
            Dim th2 As System.Threading.Thread = New Thread(AddressOf p2)
            th2.Start(tcs.Token)
    
            System.Threading.Thread.Sleep(1000)
    
            WriteLine("Request Cancel")
            tcs.Cancel()
            th2.Join()
    
            WriteLine(th2.ThreadState.ToString())
        End Sub
    
        Private Sub p2(ByVal o As Object)
    
            Dim token As System.Threading.CancellationToken = CType(o, System.Threading.CancellationToken)
            If token.IsCancellationRequested Then
                Return
            End If
    
            WriteLine("CppFunc2 Start")
            Try
                Using ev As New System.Threading.ManualResetEvent(False)
                    Using reg = token.Register(New Action(Sub() 'tokenがキャンセルになったら呼び出される関数を登録
                                                              WriteLine("CppFunc2 Cancel")
                                                              ev.Set()
                                                          End Sub))
    
                        Using waitHandle As Microsoft.Win32.SafeHandles.SafeWaitHandle = ev.SafeWaitHandle()
                            Dim handle As IntPtr = waitHandle.DangerousGetHandle()
    
                            If token.IsCancellationRequested Then
                                Return
                            End If
    
                            CppFunc2(handle) '中止を通知するためのイベントハンドルをC++側に渡す
    
                            If token.IsCancellationRequested Then
                                Return
                            End If
    
                            CppFunc2(handle) '中止を通知するためのイベントハンドルをC++側に渡す
    
                        End Using 'waitHandle
    
                    End Using 'reg
                End Using 'ev
            Catch ex As System.Threading.ThreadAbortException
                WriteLine("CppFunc2 Abort")
            Catch
                WriteLine("CppFunc2 Error")
            Finally
                WriteLine("CppFunc2 Finally")
            End Try
            WriteLine("CppFunc2 End")
        End Sub
    
        Private Sub CppFunc2(waithandle As IntPtr)
            '例としてWin32 APIのWaitForSingleObject
            WaitForSingleObject(waithandle, 10000) 'WaitHandleがSetされたら時間前でも解除される
        End Sub
    
        <Runtime.InteropServices.DllImport("kernel32.dll", SetLastError:=True)>
        Private Function WaitForSingleObject(handle As IntPtr, dwMilliseconds As Integer) As Integer
        End Function
    End Module
    
    1 人がこの回答が役に立ったと思いました。

1 件の追加の回答

並べ替え方法: 最も役に立つ
  1. huahi11115 340 評価のポイント
    2024-09-26T22:39:22.4366667+00:00

    御回答ありがとうございます。

    補足しますが、C++ライブラリーは私が作成したものではなく、実に汎用性の高い、広く復旧しているものです。三角関数sin(n)の演算速度が、場合により非常に遅いという状況とお考え下さい。

    従って、内部でどのような処理を行っているかは解析できませんが、こちらではターゲットPCや動作条件を様々に変えたランニングテストを行っており、いつAbort()を行っても問題が無いことを確認しています。

    VB.netによる分かりやすいサンプルプログラムを作成いただきありがとうございます。本日、動作させて実験を行います。結果は後日報告します。

    0 件のコメント コメントはありません

お客様の回答

回答は、質問作成者が [承諾された回答] としてマークできます。これは、ユーザーが回答が作成者の問題を解決したことを知るのに役立ちます。