3

スレッド/プロセスが強制終了された場合に実行が突然停止することは理にかなっています

ターミナル ウィンドウで [X] をクリックしてメイン プログラムを正常に終了すると、クリーンアップ コードが実行されないのはなぜですか?


私はまだマルチスレッド アプリケーションの詳細を学んでいます。私の問題は、Python がバックグラウンド スレッドの強制終了を処理する方法を理解していないことが原因だと思います。

質問:

  1. finally:ブロックが常に実行されないのはなぜですか?
  2. 他にfinally:ブロックが実行されないのはいつですか?
  3. スレッドが強制終了されると、スレッド内のコード実行はどうなりますか?
  4. メイン プロセスを終了すると、デーモン/非デーモン スレッドはどうなりますか?

詳細:

ZMQ ソケットを使用して (とりわけ) ログ ファイルに内容を書き込むマルチスレッド プログラムを作成しようとしています。ロギング スレッドが終了する直前に無条件にメッセージとクリーンアップを実行するようにしたいのですが、ほとんどの場合はそうではありません。

以下の関数は、バックグラウンド スレッドで無限ループを開始し、zmq.PAIR通信用のソケットを返します。それが開始するループはソケットをリッスンし、そのソケットに書き込まれたものはすべてファイルに書き込まれます。ループはまた、「I'm started to log now!」、「おっと、エラーが発生しました!」などの診断メッセージを送り返す(はずです)。「私は今出ています」。そのため、メインプログラムはそれを監視できます。

プログラムは、このmainパターンを使用していくつかのスレッドを生成し、さまざまな部分を監視/制御します。いくつかの ZMQ ソケット (STDIN とシリアル ポートに接続) でメッセージをポーリングし、その一部をファイルに接続されたソケットに転送します。

しかし今、私は立ち往生しています。プログラムのmainルーティングと制御ロジックは正常に動作します。get_logfile_sockのファイル書き込みは正常に機能し、通常の例外処理は期待どおりに機能します。しかし、スレッドがメイン プログラムから強制終了された場合、またはメイン プログラムを完全に停止した場合、「I'm exiting now」コードは実行されません。

例:

def get_logfile_sock(context, file_name):
    """
    Returns a ZMQ socket. Anything written to the socket gets appended to the a specified file. The socket will send diagnostic messages about file opening/closing and any exceptions encountered. 

    """

    def log_file_loop(socket):
        """
        Read characters from `socket` and write them to a file. Send back diagnostic and exception information.
        """
        try:
            socket.send("Starting Log File {}".format(file_name))
            with open(file_name, "a+") as fh:
                # File must start with a timestamp of when it was opened
                fh.write('[{}]'.format(get_timestamp()))
                # Write all strings/bytes to the file
                while True:
                    message = socket.recv()

                    fh.write(message)
                    fh.flush()

                    # Un-comment this line to demonstrate that the except: and finally: blocks both get executed when there's an error in the loop
                    # raise SystemExit

        except Exception as e:
            # This works fine when/if there's an exception in the loop
            socket.send("::".join(['FATALERROR', e.__class__.__name__, e.message]))
        finally:
            # This works fine if there's an exception raised in the loop
            # Why doesn't this get executed when my program exits? Isn't that just the main program raising SystemExit? 

            # Additional cleanup code goes here
            socket.send("Closing socket to log file {}".format(file_name))
            socket.close()


    # Make a socket pair for communication with the loop thread
    basename = os.path.basename(file_name).replace(":", "").replace(" ", "_").replace(".", "")
    SOCKNAME = 'inproc://logfile-{}'.format(basename)
    writer = context.socket(zmq.PAIR)
    reader = context.socket(zmq.PAIR)
    writer.bind(SOCKNAME)
    reader.connect(SOCKNAME)

    # Start the loop function in a separate thread
    thread = threading.Thread(target=log_file_loop, args=[writer])
    thread.daemon = True  # is this the right thing to do?
    thread.start()

    # Return a socket endpoint to the thread
    return reader
4

2 に答える 2

0

try: / finally: 必要に応じて機能するようにする方法

ベスト プラクティスは、独自のシグナリング レイヤーを作成することです (これにより、ソフト SigKILL シグナルの送受信を含む多くのことが可能になります)。

これにより、プロセス間メッセージング アーキテクチャが「クリーン」になり、完全に制御できるようになります。

ソフト SigKILL を受信すると、スレッド コードは必要なすべての手順を処理できます。独自のサブタイプの例外を発生させます。これは、意図した例外関連の構造の下で意味があります。

try:
   # ... primary flow of a <code-block>-execution
   if ( SigINPUT == "SigKILL" ):
      raise SigKILL_EXCEPTION
except KeyboardInterrupt:
   # ... handle KeyboardInterrupt

except MemoryError:
   # ... handle MemoryError

except NotImplemented:
   # ... handle NotImplemented

except SigKILL_EXCEPTION:
   # ... handle SigKILL_EXCEPTION
   # situation-specific <code-block> shall rather be here, than in "finally:"

   # /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
except:
   # ... handle *EXC
finally:
   # +++ ALWAYS DO THIS |||||||||||||||||||||||||||||||||||||||||||||||||||||
   #
   # ... a common <code-block> is ALWAYS executed, under all circumstances
   # ->  put an attempt to RETURN into SigKILL_EXCEPTION section a test this 
   # +++ ALWAYS DO THIS |||||||||||||||||||||||||||||||||||||||||||||||||||||

FINALLY: 節のデモンストレーター

def testTryFinally():
    try:
        print "TRY:"                   # show
        raise KeyboardInterrupt        # used to simulate SigKILL
    except    KeyboardInterrupt:       # EXC. to handle   SigKILL ( emulated by KBDI )
        print  "EXC.KBDI/SigKILL"                # show
        print  "EXC.KBDI:Going to RET(SigKILL)"  # remind the next instr. RET!!
        return "EXC.KBDI:RET(SigKILL)"           # execute RET <value1>
    except:                                      # EXC. collects all unhandled EXC-s
        print  "EXC.*"                           # show
    finally:                                     # FINALLY: clause
        print  "FINALLY: entered"                # show
    return     "RET(End)"                        # execute RET <value2>

>>> testTryFinally()
TRY:
EXC.KBDI/SigKILL
EXC.KBDI:Going to RET
FINALLY: entered
EXC.KBDI:RET(SigKILL)

[x]-window-frame-icon がクリックされたときにクリーンアップ コードを実行する方法

ウィンドウ フレームの右上にある [X]-window-frame-icon のクリックを処理するには、Tkinter で利用できる優れたソリューションがあります。そこで、このイベントを特別なコード ( anEventHANDLER ) によって処理されるように割り当てることができます。このコードは、そのようなキリング キスに耐えることができ、すべての汚いことを責任を持って実行します (すべてのリソースを適切に解放するように注意することを含む)。 OSによって。

Syntax:
win.protocol( 'WM_DELETE_WINDOW', lambda:None ) # blocks this way to terminate
win.protocol( 'WM_DELETE_WINDOW', aSendSigKILL_eventHANDLER )

プロセス間のソフト シグナリングを作成すると、ソフト SIG を制御およびディスパッチして、すべての分散スレッドが SIG メッセージを取得し、それに応じて独自の実行を処理できるようになります。

于 2014-10-02T17:53:05.083 に答える