70162


処理の進捗を示すダイアログを自作したのですが、各クラスのデストラクタが呼び出されず終了できません。  
最下行にある '本当に終了' は呼び出されますが、各クラスの__del__は呼び出されません。

**判明していること**

  * class Dialog()のdestroy(),forced_destroy()の最下行のprint文が呼ばれません。
  * class Dialog()のdestroy(),forced_destroy()のself.dialog.destroy()->self.dialog.quit()に変更すると、各クラスの__del__は呼び出されませんが、終了はできます。
  * class Dialog()の__init__内にある以下部分を変更すると、各クラスの__del__は呼び出されます。

[code]

            #コメントアウト
            #self.dialog.protocol("WM_DELETE_WINDOW", self.forced_termination)
    
            self.instThr = class_ProgressThread(parent=self)
            self.instThr.run() #追加
    
            #コメントアウト
            #self.dialog.after(500,self.run)
            self.dialog.mainloop()
    
[/code]

**ここからソースコード**  
テスト用のコードで単一のファイルですが、分割して掲載します。

[code]

    import time
    import threading
    
    import tkinter as tk
    from tkinter  import messagebox
    
    
[/code]

[code]

    class Dialog():
        def __init__(self):
            W, H = 0, 1
            self.dlg_size = (400,110)
            self.dialog = tk.Tk()
    
            import ctypes
            user32 = ctypes.windll.user32
            screensize = user32.GetSystemMetrics(W), user32.GetSystemMetrics(H)
            px = int(screensize[W]/2 - self.dlg_size[W]/2)
            py = int(screensize[H]/2 - self.dlg_size[H]/2)
    
            self.dialog.geometry('{}x{}+{}+{}'.format(self.dlg_size[W],self.dlg_size[H],px,py))
    
            self.dialog.resizable(0,0)
    
            self.createDialog()
    
            self.isDestroy = False
    
            self.dialog.protocol("WM_DELETE_WINDOW", self.forced_termination)
    
            self.instThr = class_ProgressThread(parent=self)
    
            self.dialog.after(500,self.run)
            self.dialog.mainloop()
    
            print('Dialog終了')
    
        def createDialog(self):
    
            W, H = 0, 1
            sp = 6
            boder = 3
    
            canvas_w = self.dlg_size[W] - boder*2 - sp*2
            canvas_h = self.dlg_size[H] - boder*2 - sp*2
    
            fnt_size = 8
            fnt_family = "游ゴシック"
            fnt_weight = "normal"
            fnt_slant = "normal"
            fnt_underlinr = "normal"
            fnt_overstrike = "normal"
    
            progress_font = (
                fnt_family,
                fnt_size,
                fnt_weight,
                fnt_slant,
                fnt_underlinr,
                fnt_overstrike
            )
    
            fnt_size = 10
    
            info_font = (
                fnt_family,
                fnt_size,
                fnt_weight,
                fnt_slant,
                fnt_underlinr,
                fnt_overstrike
            )
    
            _InFrame_ = tk.Frame(
                self.dialog,
                relief='flat',
            )
    
            self.canvas_Progress = tk.Canvas(
                _InFrame_,
            )
    
            _InFrame_.pack()
    
            self.canvas_Progress.grid(row=1, column=1, sticky=tk.W)
            self.canvas_Progress['width'] = canvas_w - boder*4
            self.canvas_Progress['height'] = canvas_h - boder*4
    
            canvas = self.canvas_Progress
    
            self.DivInfoText = canvas.create_text(
                canvas_w/2, 16,
                text='実行中',
                font=info_font,
                anchor=tk.CENTER,
            )
    
            self.progress_w = canvas_w - boder*4 - sp*2
            self.progress_h = 16
    
            Tbl_progress_bar = canvas.create_rectangle(
                sp, 36, sp+self.progress_w, 36 + self.progress_h,
                outline='gray',
                fill='gray',
                width=1,
                tags='Table',
            )
    
            param_progress_bar = canvas.create_rectangle(
                sp, 64, sp+self.progress_w, 64 + self.progress_h,
                outline='gray',
                fill='gray',
                width=1,
                tags='Param',
            )
    
            self.Tbl_progress_bar_coords = [sp, 36, sp, 36 + self.progress_h]
    
            self.Tbl_progress_bar = canvas.create_rectangle(
                self.Tbl_progress_bar_coords[0],
                self.Tbl_progress_bar_coords[1],
                self.Tbl_progress_bar_coords[2],
                self.Tbl_progress_bar_coords[3],
                fill='royalblue',
                width=0,
                tags='Table',
            )
    
            self.param_progress_bar_coords = [sp, 64, sp, 64 + self.progress_h]
    
            self.param_progress_bar = canvas.create_rectangle(
                self.param_progress_bar_coords[0],
                self.param_progress_bar_coords[1],
                self.param_progress_bar_coords[2],
                self.param_progress_bar_coords[3],
                fill='royalblue',
                width=0,
                tags='Param',
            )
    
            self.Tbl_progress_text = canvas.create_text(
                canvas_w/2, 36+8,
                text='0%',
                font=progress_font,
                fill = 'white',
                anchor=tk.CENTER,
            )
    
            self.param_progress_text = canvas.create_text(
                canvas_w/2, 64+8,
                text='0%',
                font=progress_font,
                fill = 'white',
                anchor=tk.CENTER,
            )
    
            self.dialog.update_idletasks()
            self.dialog.grab_set()
    
            return
    
        def set_progress(self,Tbl_progress:float,Param_progress:float):
            sp = 6
    
            if self.isDestroy:
                return not self.isDestroy
    
            if Tbl_progress < 0.0 or Tbl_progress > 1.0 \
                or Tbl_progress < 0.0 or Tbl_progress > 1.0 :
                return
            self.canvas_Progress.itemconfigure(
                self.Tbl_progress_text,
                text = '{}%'.format(int(Tbl_progress*100))
            )
            self.canvas_Progress.itemconfigure(
                self.param_progress_text,
                text = '{}%'.format(int(Param_progress*100))
            )
    
            self.param_progress_bar_coords[2] = int((sp+self.progress_w)*Param_progress)
            sx, sy, ex, ey = self.param_progress_bar_coords
            self.canvas_Progress.coords(
                self.param_progress_bar,
                sx, sy, ex, ey
                )
    
            self.Tbl_progress_bar_coords[2] = int((sp+self.progress_w)*Tbl_progress)
            sx, sy, ex, ey = self.Tbl_progress_bar_coords
            self.canvas_Progress.coords(
                self.Tbl_progress_bar,
                sx, sy, ex, ey
                )
    
            self.dialog.update_idletasks()
    
            return not self.isDestroy
    
        def destroy(self):
            self.isDestroy = True
            messagebox.showinfo('完了','正常に終了しました。')
            self.dialog.destroy()
            print('この後、どうしよう')
    
        def forced_destroy(self):
            self.dialog.destroy()
            print('この後、どうするの？')
    
        def forced_termination(self):
            ret = messagebox.askyesno('確認','正常に完了していません。\n強制終了しますか？')
            if ret:
                self.isDestroy = True
    
        def run(self):
            self.instThr.start()
            return
    
        def abandonment_restore(self):
            messagebox.showwarning('中断','正常に完了しませんでした。')
            return
    
        def Notice_progress(self,notice_info:dict):
            tbl_progress = notice_info['progress_table_number']/notice_info['progress_max_table']
            data_progress = notice_info['progress_data_number']/notice_info['progress_max_data']
    
            return self.set_progress(tbl_progress,data_progress)
    
        def __del__(self):
            print(__name__,'Dialog終了')
    
[/code]

[code]

    class class_ProgressThread(threading.Thread):
        def __init__(self,parent):
            threading.Thread.__init__(self)
            self.parent = parent
            return
    
        def run(self):
            isSuccess = False
    
            self.progress = class_progress(notice_obj=self.parent)
            if not self.progress.run():
                self.parent.abandonment_restore()
    
                self.parent.forced_destroy()
            self.parent.destroy()
    
            return
    
        def __del__(self):
            print(__name__,'class_ProgressThread終了')
    
[/code]

[code]

    class class_progress():
        def __init__(self,notice_obj):
            self.notice_obj = notice_obj
    
        def run(self):
            isLoop = True
    
            progress_table_number = 0
            progress_max_table = 10
            progress_data_number = 0
            progress_max_data = 10
    
            def get_notice_info():
                notice_info = {
                    'progress_table_number':progress_table_number,
                    'progress_max_table':progress_max_table,
                    'progress_data_number':progress_data_number,
                    'progress_max_data':progress_max_data,
                }
                return notice_info
    
            while(isLoop):
                time.sleep(0.05)
    
                progress_data_number += 1
                if progress_data_number == progress_max_data:
                    progress_data_number = 0
                    progress_table_number += 1
                if progress_table_number == progress_max_table:
                    isLoop = False
    
                notice_info = get_notice_info()
    
                if not self.notice_obj.Notice_progress(notice_info):
                    return False
    
            return True
    
        def __del__(self):
            print(__name__,'class_progress終了')
    
[/code]

[code]

    t = Dialog()
    
    print('本当に終了')
    
[/code]


CC BY-SA 4.0
2020-09-04T08:18:15.450
70133
2020-09-05T10:23:29.957
2020-09-05T10:23:29.957
32986
32891
question
python python3 tkinter マルチスレッド
Pythonでデストラクタが呼び出されずにプログラムが終了しないのは何故でしょうか？


プログラムが終了しないのは、現状の仕組みだと`class_ProgressThread`を`thread`として起動する`start()`が呼ばれているけれども、そのスレッドを終了させる処理が無いか動いていないのが原因です。  
簡単な対策は以下のようにスレッドをデーモンとして指定することでしょう。こうするとメインスレッドが終了すると特に指定しなくてもサブの`class_ProgressThread`スレッドも終了するので、プログラムが終了するようになります。

[code]

    self.instThr = class_ProgressThread(parent=self)
    self.instThr.setDaemon(True)    #### ←この処理を追加する
    
[/code]

ただしこの方法だとデストラクタは呼ばれないようです。質問記事の判明していることの2番目の「各クラスの__del__は呼び出されませんが、終了はできます。」と同様の効果でしょうか。  
問題現象で関連する箇所を以下に抜粋します。

[code]

    class Dialog():
        def __init__(self):
            self.dialog.after(500,self.run)
            
        def run(self):
            self.instThr.start()             #### ←スレッドとして起動している
            return
        
        def destroy(self):
            self.isDestroy = True
            messagebox.showinfo('完了','正常に終了しました。')
            self.dialog.destroy()            #### ←この関数から戻っていない
            print('この後、どうしよう')
    
    class class_ProgressThread(threading.Thread):
        def run(self):
            isSuccess = False
            self.progress = class_progress(notice_obj=self.parent)
            if not self.progress.run():      #### ←この関数から戻っていない
    
[/code]

デストラクタが呼ばれるようにするには、(良いのかどうか不明ですが) スレッドとしてではなく別の方法で起動することです。  
質問記事の判明していることの3番目で「こう変更すると呼び出された」という方法ですね。

質問記事だと`self.instThr =
class_ProgressThread(parent=self)`の直後に`self.instThr.run()`していますが、そうではなくてその下のコメントアウトしている`.after`の呼び出し先を`class
Dialog():`の`def run(self):`から、`class
class_ProgressThread(threading.Thread):`の`def run(self):`に変更すれば良いでしょう。  
以下のようになります。

[code]

    self.dialog.after(500,self.run)
                               ↓↓
    self.dialog.after(500,self.instThr.run)
    
[/code]


CC BY-SA 4.0
2020-09-05T09:39:55.983
70162
2020-09-05T09:39:55.983
26370
70133
answer