1111import threading
1212
1313
14+ class _ProgressEvent (QEvent ):
15+ _type = QEvent .Type (QEvent .registerEventType ())
16+
17+ def __init__ (self , n , t ):
18+ super ().__init__ (self ._type )
19+ self .n = n
20+ self .t = t
21+
22+
1423class WorkerSignals (QObject ):
1524 '''
1625 Defines the signals available from a running worker thread.
@@ -27,14 +36,20 @@ class WorkerSignals(QObject):
2736 `object` data returned from processing, anything
2837
2938 progress
30- `int` indicating % progress
39+ `int` indicating % progress
3140
3241 '''
3342 finished = Signal ()
3443 error = Signal (tuple )
3544 result = Signal (object )
3645 progress = Signal (int , int )
3746
47+ def event (self , e ):
48+ if e .type () == _ProgressEvent ._type :
49+ self .progress .emit (e .n , e .t )
50+ return True
51+ return super ().event (e )
52+
3853
3954class Worker (QRunnable ):
4055 '''
@@ -59,8 +74,33 @@ def __init__(self, fn, *args, **kwargs):
5974 self .kwargs = kwargs
6075 self .signals = WorkerSignals ()
6176
77+ # ORIGINAL METHOD
6278 # Add the callback to our kwargs
63- self .kwargs ['progress_callback' ] = lambda n , t : self .signals .progress .emit (n , t )
79+ # self.kwargs['progress_callback'] = lambda n, t: self.signals.progress.emit(n, t)
80+
81+ # SECOND WAY, STILL FAILED
82+ # Add the callback to our kwargs.
83+ # Use QTimer.singleShot with self.signals as the context object so the
84+ # emission is always marshalled to the GUI thread, regardless of which
85+ # thread calls the callback. Directly emitting Signal(int, int) from a
86+ # non-GUI thread triggers a PyQt6 bug in its typed-argument marshaling
87+ # that corrupts the heap and causes hard crashes in unrelated threads.
88+ # _signals = self.signals
89+ # self.kwargs['progress_callback'] = lambda n, t: QTimer.singleShot(
90+ # 0, _signals, lambda: _signals.progress.emit(n, t)
91+ # )
92+
93+ # NEW WAY
94+ # Add the callback to our kwargs.
95+ # Use QTimer.singleShot with self.signals as the context object so the
96+ # emission is always marshalled to the GUI thread, regardless of which
97+ # thread calls the callback. Directly emitting Signal(int, int) from a
98+ # non-GUI thread triggers a PyQt6 bug in its typed-argument marshaling
99+ # that corrupts the heap and causes hard crashes in unrelated threads.
100+ _signals = self .signals
101+ self .kwargs ['progress_callback' ] = lambda n , t : QApplication .postEvent (
102+ _signals , _ProgressEvent (n , t )
103+ )
64104
65105 # Cancellation event
66106 self .cancel_event = threading .Event ()
0 commit comments