diff --git a/docs/standard/garbage-collection/implementing-dispose.md b/docs/standard/garbage-collection/implementing-dispose.md index d2fd4f5845170..7030ddb3057e9 100644 --- a/docs/standard/garbage-collection/implementing-dispose.md +++ b/docs/standard/garbage-collection/implementing-dispose.md @@ -91,7 +91,7 @@ All non-sealed classes (or Visual Basic classes not modified as `NotInheritable` ### Base class with managed resources -Here's a general example of implementing the dispose pattern for a base class that only owns managed resources. +Here's a general example of implementing the dispose pattern for a base class that only owns managed resources. The example uses to ensure thread-safe disposal. :::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs"::: :::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb"::: @@ -153,7 +153,7 @@ The following code demonstrates how to handle unmanaged resources by implementin > The behavior of the `DisposableBaseWithSafeHandle` class is equivalent to the behavior of the [`DisposableBaseWithFinalizer` class in a previous example](#base-class-with-unmanaged-resources), however the approach demonstrated here is safer: > > - There is no need to implement a finalizer, because will take care of finalization. -> - There is no need for synchronization to guarantee thread safety. Even though there is a race condition in the `Dispose` implementation of `DisposableBaseWithSafeHandle`, guarantees that will be called only once. +> - guarantees that will be called only once, even in multi-threaded scenarios. ### Built-in safe handles in .NET diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs index 182367f60ac73..2f00f7a0190ea 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs @@ -1,10 +1,13 @@ using System; using System.IO; +using System.Threading; public class DisposableBase : IDisposable { - // Detect redundant Dispose() calls. - private bool _isDisposed; + // Detect redundant Dispose() calls in a thread-safe manner. + // _isDisposed == 0 means Dispose(bool) has not been called yet. + // _isDisposed == 1 means Dispose(bool) has been already called. + private int _isDisposed; // Instantiate a disposable object owned by this class. private Stream? _managedResource = new MemoryStream(); @@ -19,10 +22,10 @@ public void Dispose() // Protected implementation of Dispose pattern. protected virtual void Dispose(bool disposing) { - if (!_isDisposed) + // In case _isDisposed is 0, atomically set it to 1. + // Enter the branch only if the original value is 0. + if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0) { - _isDisposed = true; - if (disposing) { // Dispose managed state. diff --git a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs index 9e681a3a3a53a..1ef602f8f84f9 100644 --- a/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs +++ b/samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Runtime.InteropServices; +using System.Threading; using Microsoft.Win32.SafeHandles; // Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle. @@ -27,8 +28,10 @@ public static LocalAllocHandle Allocate(int numberOfBytes) public class DisposableBaseWithSafeHandle : IDisposable { - // Detect redundant Dispose() calls. - private bool _isDisposed; + // Detect redundant Dispose() calls in a thread-safe manner. + // _isDisposed == 0 means Dispose(bool) has not been called yet. + // _isDisposed == 1 means Dispose(bool) has been already called. + private int _isDisposed; // Managed disposable objects owned by this class private LocalAllocHandle? _safeHandle = LocalAllocHandle.Allocate(10); @@ -44,10 +47,10 @@ public void Dispose() // Protected implementation of Dispose pattern. protected virtual void Dispose(bool disposing) { - if (!_isDisposed) + // In case _isDisposed is 0, atomically set it to 1. + // Enter the branch only if the original value is 0. + if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0) { - _isDisposed = true; - if (disposing) { // Dispose managed state. diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb index c35a2a80e8a03..d1b4d4c65aeac 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb @@ -1,10 +1,13 @@ Imports System.IO +Imports System.Threading Public Class DisposableBase Implements IDisposable - ' Detect redundant Dispose() calls. - Private _isDisposed As Boolean + ' Detect redundant Dispose() calls in a thread-safe manner. + ' _isDisposed = 0 means Dispose(bool) has not been called yet. + ' _isDisposed = 1 means Dispose(bool) has been already called. + Private _isDisposed As Integer ' Instantiate a disposable object owned by this class. Private _managedResource As Stream = New MemoryStream() @@ -17,9 +20,9 @@ Public Class DisposableBase ' Protected implementation of Dispose pattern. Protected Overridable Sub Dispose(disposing As Boolean) - If Not _isDisposed Then - _isDisposed = True - + ' In case _isDisposed is 0, atomically set it to 1. + ' Enter the branch only if the original value is 0. + If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 Then If disposing Then ' Dispose managed state. _managedResource?.Dispose() diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb index ce4df58ea1509..99b3d7442be25 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb +++ b/samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb @@ -1,6 +1,7 @@ Imports System Imports System.IO Imports System.Runtime.InteropServices +Imports System.Threading Imports Microsoft.Win32.SafeHandles ' Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle. @@ -29,8 +30,10 @@ End Class Public Class DisposableBaseWithSafeHandle Implements IDisposable - ' Detect redundant Dispose() calls. - Private _isDisposed As Boolean + ' Detect redundant Dispose() calls in a thread-safe manner. + ' _isDisposed = 0 means Dispose(bool) has not been called yet. + ' _isDisposed = 1 means Dispose(bool) has been already called. + Private _isDisposed As Integer ' Managed disposable objects owned by this class Private _safeHandle As LocalAllocHandle = LocalAllocHandle.Allocate(10) @@ -44,9 +47,9 @@ Public Class DisposableBaseWithSafeHandle ' Protected implementation of Dispose pattern. Protected Overridable Sub Dispose(disposing As Boolean) - If Not _isDisposed Then - _isDisposed = True - + ' In case _isDisposed is 0, atomically set it to 1. + ' Enter the branch only if the original value is 0. + If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 Then If disposing Then ' Dispose managed state. _otherUnmanagedResource?.Dispose()