mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-06 12:25:43 -05:00
fix(ui): refresh the drawing list on the UI thread, never from the timer thread
EditNestForm's debounce timer is a System.Timers.Timer whose Elapsed ran on a pool thread, checked drawingListBox1.IsHandleCreated, then called drawingListBox1.Invoke. Invoke reads Handle, so a handle destroyed between the check and the call was re-created on the pool thread (the cross-thread check is off outside the debugger) while the UI thread was disposing the window, which threw "Dispose() cannot be called while doing CreateHandle()". The timer starts enabled at 50 ms, so short WinForms tests such as LedgerCodeRecheckTests.NoLedgerOrNoCodes_ChecksNothing hit it intermittently on CI. Elapsed now posts to the window's UI synchronization context and the refresh checks IsDisposed there; the timer is disposed with the form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fbe82d198a
commit
46adbfd6f6
2 files changed
+17
-10
No files matched your search
Generated
+2
@@ -19,6 +19,8 @@
|
||||
// The simulator's lock disables the main window until it closes.
|
||||
if (disposing)
|
||||
cutSimulatorForm?.Close();
|
||||
if (disposing)
|
||||
updateDrawingListTimer?.Dispose();
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
|
||||
@@ -49,6 +49,12 @@ namespace OpenNest.Forms
|
||||
public readonly NestHoldStatus HoldStatus;
|
||||
|
||||
private readonly Timer updateDrawingListTimer;
|
||||
|
||||
// The window's UI thread. updateDrawingListTimer fires on a pool thread, which must
|
||||
// never touch a control: Control.Invoke reads Handle, and a handle destroyed between
|
||||
// the check and the call is then re-created on the pool thread while this window is
|
||||
// being disposed ("Dispose() cannot be called while doing CreateHandle()").
|
||||
private readonly SynchronizationContext uiContext;
|
||||
private bool updatingPlateList;
|
||||
private bool addingToSet;
|
||||
private CutSimulatorForm cutSimulatorForm;
|
||||
@@ -457,6 +463,7 @@ namespace OpenNest.Forms
|
||||
Interval = 50,
|
||||
};
|
||||
updateDrawingListTimer.Elapsed += drawingListUpdateTimer_Elapsed;
|
||||
uiContext = SynchronizationContext.Current;
|
||||
|
||||
Document = new Document { Nest = nest };
|
||||
HoldStatus = new NestHoldStatus(Document);
|
||||
@@ -1790,14 +1797,13 @@ namespace OpenNest.Forms
|
||||
updateDrawingListTimer.Start();
|
||||
}
|
||||
|
||||
private void drawingListUpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
if (!drawingListBox1.IsHandleCreated)
|
||||
return;
|
||||
|
||||
drawingListBox1.Invoke(
|
||||
new MethodInvoker(() =>
|
||||
private void drawingListUpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) =>
|
||||
uiContext?.Post(_ =>
|
||||
{
|
||||
// Runs on the UI thread, so this check cannot race the window's disposal.
|
||||
if (IsDisposed || Disposing || !drawingListBox1.IsHandleCreated)
|
||||
return;
|
||||
|
||||
if (hideNestedButton.Checked)
|
||||
{
|
||||
drawingListBox1.BeginUpdate();
|
||||
@@ -1827,9 +1833,8 @@ namespace OpenNest.Forms
|
||||
// part-count and utilization columns stay current. The debounce
|
||||
// timer collapses a fill's many per-part events into one refresh.
|
||||
UpdatePlateList();
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
null);
|
||||
|
||||
private void drawingListBox1_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user