From de9af8fa29d8faf10ac2ae61ca2179e58186e0f3 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Thu, 8 Aug 2024 11:48:29 -0700 Subject: [PATCH] Change locks to System.Threading.Lock (#11841) Change internal lock objects to System.Threading.Lock Fixes #11672 --- src/System.Drawing.Common/src/GlobalUsings.cs | 4 + .../src/System/Drawing/Graphics.cs | 2 +- .../Drawing/Internal/SystemColorTracker.cs | 2 +- .../src/System/Drawing/Lock.cs | 11 +++ .../Design/MenuCommandService.cs | 3 +- .../tests/TestUtilities/NoAssertContext.cs | 2 +- .../Forms/Application.ThreadContext.cs | 4 +- .../src/System/Windows/Forms/Application.cs | 2 +- .../Forms/Control.ThreadMethodEntry.cs | 37 ++-------- .../ImageList/ImageList.NativeImageList.cs | 2 +- .../Controls/ImageList/ImageListStreamer.cs | 2 +- .../Forms/Controls/PictureBox/PictureBox.cs | 2 +- .../Controls/ToolStrips/ToolStripManager.cs | 2 +- .../Windows/Forms/DataBinding/Command.cs | 10 +-- .../src/System/Windows/Forms/Form.cs | 25 ++----- .../src/System/Windows/Forms/NativeWindow.cs | 4 +- .../src/System/Windows/Forms/NotifyIcon.cs | 4 +- .../System/Windows/Forms/OLE/DataFormats.cs | 2 +- .../Windows/Forms/OwnerDrawPropertyBag.cs | 2 +- .../Windows/Forms/Rendering/FontCache.cs | 2 +- .../src/System/Windows/Forms/Screen.cs | 2 +- .../System/Windows/Forms/SendKeys/SendKeys.cs | 2 +- .../src/System/Windows/Forms/Timer.cs | 73 ++++++++++--------- .../Infra/ScreenshotService.cs | 4 +- 24 files changed, 90 insertions(+), 115 deletions(-) create mode 100644 src/System.Drawing.Common/src/System/Drawing/Lock.cs diff --git a/src/System.Drawing.Common/src/GlobalUsings.cs b/src/System.Drawing.Common/src/GlobalUsings.cs index c7e6245d339..17bff663bd1 100644 --- a/src/System.Drawing.Common/src/GlobalUsings.cs +++ b/src/System.Drawing.Common/src/GlobalUsings.cs @@ -13,6 +13,10 @@ global using Windows.Win32.System.Memory; global using Windows.Win32.UI.WindowsAndMessaging; +#if NET9_0_OR_GREATER +global using Lock = System.Threading.Lock; +#endif + global using BitmapData = System.Drawing.Imaging.BitmapData; global using ColorPalette = System.Drawing.Imaging.ColorPalette; global using DashCap = System.Drawing.Drawing2D.DashCap; diff --git a/src/System.Drawing.Common/src/System/Drawing/Graphics.cs b/src/System.Drawing.Common/src/System/Drawing/Graphics.cs index e5f16e4be0b..a7f25e71c18 100644 --- a/src/System.Drawing.Common/src/System/Drawing/Graphics.cs +++ b/src/System.Drawing.Common/src/System/Drawing/Graphics.cs @@ -25,7 +25,7 @@ public sealed unsafe partial class Graphics : MarshalByRefObject, IDisposable, I /// private GraphicsContext? _previousContext; - private static readonly object s_syncObject = new(); + private static readonly Lock s_syncObject = new(); // Object reference used for printing; it could point to a PrintPreviewGraphics to obtain the VisibleClipBounds, or // a DeviceContext holding a printer DC. diff --git a/src/System.Drawing.Common/src/System/Drawing/Internal/SystemColorTracker.cs b/src/System.Drawing.Common/src/System/Drawing/Internal/SystemColorTracker.cs index b014a467781..b6d2d61878c 100644 --- a/src/System.Drawing.Common/src/System/Drawing/Internal/SystemColorTracker.cs +++ b/src/System.Drawing.Common/src/System/Drawing/Internal/SystemColorTracker.cs @@ -19,7 +19,7 @@ internal static class SystemColorTracker private static WeakReference?[] s_list = new WeakReference?[INITIAL_SIZE]; private static int s_count; private static bool s_addedTracker; - private static readonly object s_lockObject = new(); + private static readonly Lock s_lockObject = new(); internal static void Add(ISystemColorTracker obj) { diff --git a/src/System.Drawing.Common/src/System/Drawing/Lock.cs b/src/System.Drawing.Common/src/System/Drawing/Lock.cs new file mode 100644 index 00000000000..0a66a53ed35 --- /dev/null +++ b/src/System.Drawing.Common/src/System/Drawing/Lock.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Drawing; + +#if !NET9_0_OR_GREATER +// System.Threading.Lock stub for NET8.0 and below +internal class Lock +{ +} +#endif diff --git a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/MenuCommandService.cs b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/MenuCommandService.cs index 16a735d1c98..1be34b77148 100644 --- a/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/MenuCommandService.cs +++ b/src/System.Windows.Forms.Design/src/System/ComponentModel/Design/MenuCommandService.cs @@ -17,7 +17,7 @@ public class MenuCommandService : IMenuCommandService, IDisposable { private IServiceProvider? _serviceProvider; private readonly Dictionary> _commandGroups; - private readonly object _commandGroupsLock; + private readonly Lock _commandGroupsLock = new(); private readonly EventHandler _commandChangedHandler; private MenuCommandsChangedEventHandler? _commandsChangedHandler; private List? _globalVerbs; @@ -38,7 +38,6 @@ public class MenuCommandService : IMenuCommandService, IDisposable public MenuCommandService(IServiceProvider? serviceProvider) { _serviceProvider = serviceProvider; - _commandGroupsLock = new object(); _commandGroups = []; _commandChangedHandler = new EventHandler(OnCommandChanged); TypeDescriptor.Refreshed += new RefreshEventHandler(OnTypeRefreshed); diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/NoAssertContext.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/NoAssertContext.cs index 71489d6ca3b..dcf564ddf77 100644 --- a/src/System.Windows.Forms.Primitives/tests/TestUtilities/NoAssertContext.cs +++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/NoAssertContext.cs @@ -20,7 +20,7 @@ public sealed class NoAssertContext : IDisposable // We do, however need to lock around hooking/unhooking our custom listener to make sure that we // are rerouting correctly if multiple threads are creating/disposing this class concurrently. - private static readonly object s_lock = new(); + private static readonly Lock s_lock = new(); private static bool s_hooked; private static bool s_hasDefaultListener; private static bool s_hasThrowingListener; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs index db127631ee4..24bf799e9e6 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs @@ -24,9 +24,7 @@ internal abstract unsafe partial class ThreadContext : MarshalByRefObject, IHand private static readonly Dictionary s_contextHash = []; - // When this gets to zero, we'll invoke a full garbage - // collect and check for root/window leaks. - private static readonly object s_tcInternalSyncObject = new(); + private static readonly Lock s_tcInternalSyncObject = new(); private static int s_totalMessageLoopCount; private static msoloop s_baseLoopReason; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs index 7db4f080db3..aea8f695fed 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs @@ -40,7 +40,7 @@ public sealed partial class Application private static bool s_comCtlSupportsVisualStylesInitialized; private static bool s_comCtlSupportsVisualStyles; private static FormCollection? s_forms; - private static readonly object s_internalSyncObject = new(); + private static readonly Lock s_internalSyncObject = new(); private static bool s_useWaitCursor; /// diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Control.ThreadMethodEntry.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Control.ThreadMethodEntry.cs index 0fff481efa5..9d7acffbc59 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Control.ThreadMethodEntry.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Control.ThreadMethodEntry.cs @@ -18,7 +18,7 @@ private class ThreadMethodEntry : IAsyncResult internal Exception? _exception; internal bool _synchronous; private ManualResetEvent? _resetEvent; - private readonly object _invokeSyncObject = new(); + private readonly Lock _invokeSyncObject = new(); // Store the execution context associated with the caller thread, and // information about which thread actually got the stack applied to it. @@ -44,13 +44,7 @@ internal ThreadMethodEntry( _executionContext = executionContext; } - public object? AsyncState - { - get - { - return null; - } - } + public object? AsyncState => null; public WaitHandle AsyncWaitHandle { @@ -58,19 +52,13 @@ public WaitHandle AsyncWaitHandle { if (_resetEvent is null) { - // Locking 'this' here is ok since this is an internal class. lock (_invokeSyncObject) { - // BeginInvoke hangs on Multi-proc system: - // taking the lock prevents a race condition between IsCompleted - // boolean flag and resetEvent mutex in multiproc scenarios. - if (_resetEvent is null) + _resetEvent ??= new ManualResetEvent(false); + + if (IsCompleted) { - _resetEvent = new ManualResetEvent(false); - if (IsCompleted) - { - _resetEvent.Set(); - } + _resetEvent.Set(); } } } @@ -79,18 +67,7 @@ public WaitHandle AsyncWaitHandle } } - public bool CompletedSynchronously - { - get - { - if (IsCompleted && _synchronous) - { - return true; - } - - return false; - } - } + public bool CompletedSynchronously => IsCompleted && _synchronous; public bool IsCompleted { get; private set; } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ImageList/ImageList.NativeImageList.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ImageList/ImageList.NativeImageList.cs index a8b80f47d0f..54cf5036e0a 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ImageList/ImageList.NativeImageList.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ImageList/ImageList.NativeImageList.cs @@ -16,7 +16,7 @@ internal sealed class NativeImageList : IDisposable, IHandle private const int GrowBy = 4; private const int InitialCapacity = 4; - private static readonly object s_syncLock = new(); + private static readonly Lock s_syncLock = new(); public unsafe NativeImageList(IStream.Interface pstm) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ImageList/ImageListStreamer.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ImageList/ImageListStreamer.cs index 05b7d16dc93..079b61fb2e0 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ImageList/ImageListStreamer.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ImageList/ImageListStreamer.cs @@ -13,7 +13,7 @@ public sealed class ImageListStreamer : ISerializable, IDisposable { // Compressed magic header. If we see this, the image stream is compressed. private static ReadOnlySpan HeaderMagic => "MSFt"u8; - private static readonly object s_syncObject = new(); + private static readonly Lock s_syncObject = new(); private readonly ImageList? _imageList; private ImageList.NativeImageList? _nativeImageList; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/PictureBox/PictureBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/PictureBox/PictureBox.cs index 2855cb0b6f8..8579c6a2420 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/PictureBox/PictureBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/PictureBox/PictureBox.cs @@ -64,7 +64,7 @@ public partial class PictureBox : Control, ISupportInitialize private SendOrPostCallback? _loadCompletedDelegate; private SendOrPostCallback? _loadProgressDelegate; private bool _handleValid; - private readonly object _internalSyncObject = new(); + private readonly Lock _internalSyncObject = new(); // These default images will be demand loaded. private Image? _defaultInitialImage; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripManager.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripManager.cs index f8b37e9aa01..50d160fe906 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripManager.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ToolStrips/ToolStripManager.cs @@ -32,7 +32,7 @@ public static partial class ToolStripManager private const int StaticEventDefaultRendererChanged = 0; private const int StaticEventCount = 1; - private static readonly object s_internalSyncObject = new(); + private static readonly Lock s_internalSyncObject = new(); private static void InitializeThread() { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/DataBinding/Command.cs b/src/System.Windows.Forms/src/System/Windows/Forms/DataBinding/Command.cs index 90da945d49d..991fa14ba73 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/DataBinding/Command.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/DataBinding/Command.cs @@ -7,7 +7,7 @@ internal class Command : WeakReference { private static Command?[]? s_cmds; private static int s_icmdTry; - private static readonly object s_internalSyncObject = new(); + private static readonly Lock s_internalSyncObject = new(); private const int IdMin = 0x00100; private const int IdLim = 0x10000; @@ -19,13 +19,7 @@ public Command(ICommandExecutor target) AssignID(this); } - public virtual int ID - { - get - { - return _id; - } - } + public virtual int ID => _id; protected static void AssignID(Command cmd) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs index 3e05ec2af11..02006e8ed10 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs @@ -97,7 +97,7 @@ public partial class Form : ContainerControl private const int SizeGripSize = 16; private static Icon? s_defaultIcon; - private static readonly object s_internalSyncObject = new(); + private static readonly Lock s_internalSyncObject = new(); // Property store keys for properties. The property store allocates most efficiently // in groups of four, so we try to lump properties in groups of four based on how @@ -886,13 +886,12 @@ internal static Icon DefaultIcon { get { - // Avoid locking if the value is filled in... + // Avoid locking if the value is filled in. if (s_defaultIcon is null) { lock (s_internalSyncObject) { - // Once we grab the lock, we re-check the value to avoid a - // race condition. + // Once we grab the lock, we re-check the value to avoid a race condition. s_defaultIcon ??= new Icon(typeof(Form), "wfc"); } } @@ -901,25 +900,13 @@ internal static Icon DefaultIcon } } - protected override ImeMode DefaultImeMode - { - get - { - return ImeMode.NoControl; - } - } + protected override ImeMode DefaultImeMode => ImeMode.NoControl; /// /// Deriving classes can override this to configure a default size for their control. /// This is more efficient than setting the size in the control's constructor. /// - protected override Size DefaultSize - { - get - { - return new Size(300, 300); - } - } + protected override Size DefaultSize => new Size(300, 300); /// /// Gets or sets the size and location of the form on the Windows desktop. @@ -938,7 +925,6 @@ public Rectangle DesktopBounds bounds.Y -= screen.Y; return bounds; } - set { SetDesktopBounds(value.X, value.Y, value.Width, value.Height); @@ -962,7 +948,6 @@ public Point DesktopLocation loc.Y -= screen.Y; return loc; } - set { SetDesktopLocation(value.X, value.Y); diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/NativeWindow.cs b/src/System.Windows.Forms/src/System/Windows/Forms/NativeWindow.cs index 97e06e649f2..3ca916156e1 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/NativeWindow.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/NativeWindow.cs @@ -37,8 +37,8 @@ public unsafe partial class NativeWindow : MarshalByRefObject, IWin32Window, IHa private static short s_globalID = 1; private static readonly Dictionary s_windowHandles = []; private static readonly Dictionary s_windowIds = []; - private static readonly object s_internalSyncObject = new(); - private static readonly object s_createWindowSyncObject = new(); + private static readonly Lock s_internalSyncObject = new(); + private static readonly Lock s_createWindowSyncObject = new(); // Our window procedure delegate private WNDPROC? _windowProc; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/NotifyIcon.cs b/src/System.Windows.Forms/src/System/Windows/Forms/NotifyIcon.cs index 71a688cac77..4a4141f9071 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/NotifyIcon.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/NotifyIcon.cs @@ -31,7 +31,7 @@ public sealed partial class NotifyIcon : Component private const int WM_TRAYMOUSEMESSAGE = (int)PInvoke.WM_USER + 1024; private static uint WM_TASKBARCREATED { get; } = PInvoke.RegisterWindowMessage("TaskbarCreated"); - private readonly object _syncObj = new(); + private readonly Lock _lock = new(); private Icon? _icon; private string _text = string.Empty; @@ -623,7 +623,7 @@ private void ShowContextMenu() /// private unsafe void UpdateIcon(bool showIconInTray) { - lock (_syncObj) + lock (_lock) { // Bail if in design mode... if (DesignMode) diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataFormats.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataFormats.cs index 3df2fbdfeb0..fdb407af0ff 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataFormats.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OLE/DataFormats.cs @@ -147,7 +147,7 @@ public static partial class DataFormats private static Format[]? s_formatList; private static int s_formatCount; - private static readonly object s_internalSyncObject = new(); + private static readonly Lock s_internalSyncObject = new(); /// /// Gets a with the Windows Clipboard numeric ID and name for the specified format. diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/OwnerDrawPropertyBag.cs b/src/System.Windows.Forms/src/System/Windows/Forms/OwnerDrawPropertyBag.cs index 0e2c6984ad4..a5e39cb6475 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/OwnerDrawPropertyBag.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/OwnerDrawPropertyBag.cs @@ -13,7 +13,7 @@ namespace System.Windows.Forms; public class OwnerDrawPropertyBag : MarshalByRefObject, ISerializable { private Control.FontHandleWrapper? _fontWrapper; - private static readonly object s_internalSyncObject = new(); + private static readonly Lock s_internalSyncObject = new(); protected OwnerDrawPropertyBag(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Rendering/FontCache.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Rendering/FontCache.cs index 509262ac8b8..e7db5083a7f 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Rendering/FontCache.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Rendering/FontCache.cs @@ -21,7 +21,7 @@ namespace System.Windows.Forms; /// internal sealed partial class FontCache : RefCountedCache { - private readonly object _lock = new(); + private readonly Lock _lock = new(); /// /// Create a with the specified collection limits. diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Screen.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Screen.cs index de1d223b813..8a337e17286 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Screen.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Screen.cs @@ -35,7 +35,7 @@ public partial class Screen private readonly int _bitDepth; - private static readonly object s_syncLock = new(); // used to lock this class before syncing to SystemEvents + private static readonly Lock s_syncLock = new(); // used to lock this class before syncing to SystemEvents private static int s_desktopChangedCount = -1; // static counter of desktop size changes diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/SendKeys/SendKeys.cs b/src/System.Windows.Forms/src/System/Windows/Forms/SendKeys/SendKeys.cs index 3e8243ea0cd..e86ed7bea9a 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/SendKeys/SendKeys.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/SendKeys/SendKeys.cs @@ -85,7 +85,7 @@ public partial class SendKeys /// private static readonly Queue s_events = new(); - private static readonly object s_lock = new(); + private static readonly Lock s_lock = new(); private static bool s_startNewChar; private static readonly SKWindow s_messageWindow; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Timer.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Timer.cs index e74c29636e9..accc59aed92 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Timer.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Timer.cs @@ -27,7 +27,7 @@ public class Timer : Component // Holder for the HWND that handles our Timer messages. private TimerNativeWindow? _timerWindow; - private readonly object _syncObj = new(); + private readonly Lock _lock = new(); /// /// Initializes a new instance of the class. @@ -91,31 +91,35 @@ public virtual bool Enabled get => _timerWindow is null ? _enabled : _timerWindow.IsTimerRunning; set { - lock (_syncObj) + lock (_lock) { - if (_enabled != value) + if (_enabled == value) { - _enabled = value; + return; + } + + _enabled = value; + + // At runtime, enable or disable the corresponding Windows timer + if (DesignMode) + { + return; + } + + if (value) + { + // Create the timer window if needed. + _timerWindow ??= new TimerNativeWindow(this); - // At runtime, enable or disable the corresponding Windows timer - if (!DesignMode) + _timerRoot = GCHandle.Alloc(this); + _timerWindow.StartTimer(_interval); + } + else + { + _timerWindow?.StopTimer(); + if (_timerRoot.IsAllocated) { - if (value) - { - // Create the timer window if needed. - _timerWindow ??= new TimerNativeWindow(this); - - _timerRoot = GCHandle.Alloc(this); - _timerWindow.StartTimer(_interval); - } - else - { - _timerWindow?.StopTimer(); - if (_timerRoot.IsAllocated) - { - _timerRoot.Free(); - } - } + _timerRoot.Free(); } } } @@ -133,23 +137,25 @@ public int Interval get => _interval; set { - lock (_syncObj) + lock (_lock) { if (value < 1) { throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(SR.TimerInvalidInterval, value, 0)); } - if (_interval != value) + if (_interval == value) + { + return; + } + + _interval = value; + if (Enabled) { - _interval = value; - if (Enabled) + // Change the timer value, don't tear down the timer itself. + if (!DesignMode && _timerWindow is not null) { - // Change the timer value, don't tear down the timer itself. - if (!DesignMode && _timerWindow is not null) - { - _timerWindow.RestartTimer(value); - } + _timerWindow.RestartTimer(value); } } } @@ -188,6 +194,8 @@ private class TimerNativeWindow : NativeWindow // Setting this when we are stopping the timer so someone can't restart it in the process. private bool _stoppingTimer; + private readonly Lock _lock = new(); + internal TimerNativeWindow(Timer owner) { _owner = owner; @@ -279,8 +287,7 @@ public void StopTimer(HWND hwnd, bool destroyHwnd) return; } - // Locking 'this' here is ok since this is an internal class. - lock (this) + lock (_lock) { if (_stoppingTimer || hwnd.IsNull || !PInvoke.IsWindow(hwnd)) { diff --git a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Infra/ScreenshotService.cs b/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Infra/ScreenshotService.cs index e7af4afc0b3..ebf441382cd 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Infra/ScreenshotService.cs +++ b/src/System.Windows.Forms/tests/IntegrationTests/UIIntegrationTests/Infra/ScreenshotService.cs @@ -8,7 +8,7 @@ namespace System.Windows.Forms.UITests; internal static class ScreenshotService { - private static readonly object s_gate = new(); + private static readonly Lock s_lock = new(); /// /// Takes a picture of the screen and saves it to the location specified by @@ -22,7 +22,7 @@ public static void TakeScreenshot(string fullPath) // 1. Only one screenshot is held in memory at a time to prevent running out of memory for large displays // 2. Only one screenshot is written to disk at a time to avoid exceptions if concurrent calls are writing // to the same file - lock (s_gate) + lock (s_lock) { using var bitmap = TryCaptureFullScreen(); if (bitmap is null)