Skip to content

Commit 9849e22

Browse files
committed
misc perf things
1 parent 9fa7c07 commit 9849e22

File tree

4 files changed

+114
-83
lines changed

4 files changed

+114
-83
lines changed

src/AsyncSingleton.cs

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Soenneker.Utils.AsyncSingleton;
1212
public class AsyncSingleton : IAsyncSingleton
1313
{
1414
private object? _instance;
15-
private readonly AsyncLock _lock;
15+
private readonly AsyncLock _lock = new();
1616

1717
private Func<ValueTask<object>>? _asyncFunc;
1818
private Func<object>? _func;
@@ -64,7 +64,6 @@ public AsyncSingleton(Func<object> func) : this()
6464

6565
public AsyncSingleton()
6666
{
67-
_lock = new AsyncLock();
6867
}
6968

7069
public ValueTask Init(params object[] objects)
@@ -77,12 +76,12 @@ public async ValueTask Init(CancellationToken cancellationToken, params object[]
7776
if (_disposed)
7877
throw new ObjectDisposedException(nameof(AsyncSingleton));
7978

80-
if (_instance != null)
79+
if (_instance is not null)
8180
return;
8281

8382
using (await _lock.LockAsync(cancellationToken).ConfigureAwait(false))
8483
{
85-
if (_instance != null)
84+
if (_instance is not null)
8685
return;
8786

8887
_instance = await InitInternal(cancellationToken, objects).NoSync();
@@ -95,32 +94,32 @@ private async ValueTask<object> InitInternal(CancellationToken cancellationToken
9594
{
9695
case InitializationType.AsyncObject:
9796
if (_asyncObjectFunc == null)
98-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
97+
throw new NullReferenceException(Constants.InitializationFuncError);
9998

10099
return await _asyncObjectFunc(objects).NoSync();
101100
case InitializationType.AsyncObjectToken:
102101
if (_asyncObjectTokenFunc == null)
103-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
102+
throw new NullReferenceException(Constants.InitializationFuncError);
104103

105104
return await _asyncObjectTokenFunc(cancellationToken, objects).NoSync();
106105
case InitializationType.Async:
107106
if (_asyncFunc == null)
108-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
107+
throw new NullReferenceException(Constants.InitializationFuncError);
109108

110109
return await _asyncFunc().NoSync();
111110
case InitializationType.SyncObject:
112111
if (_objectFunc == null)
113-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
112+
throw new NullReferenceException(Constants.InitializationFuncError);
114113

115114
return _objectFunc(objects);
116115
case InitializationType.SyncObjectToken:
117116
if (_objectTokenFunc == null)
118-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
117+
throw new NullReferenceException(Constants.InitializationFuncError);
119118

120119
return _objectTokenFunc(cancellationToken, objects);
121120
case InitializationType.Sync:
122121
if (_func == null)
123-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
122+
throw new NullReferenceException(Constants.InitializationFuncError);
124123

125124
return _func();
126125
default:
@@ -138,12 +137,12 @@ public void InitSync(CancellationToken cancellationToken, params object[] object
138137
if (_disposed)
139138
throw new ObjectDisposedException(nameof(AsyncSingleton));
140139

141-
if (_instance != null)
140+
if (_instance is not null)
142141
return;
143142

144143
using (_lock.Lock(cancellationToken))
145144
{
146-
if (_instance != null)
145+
if (_instance is not null)
147146
return;
148147

149148
_instance = InitSyncInternal(cancellationToken, objects);
@@ -156,32 +155,32 @@ private object InitSyncInternal(CancellationToken cancellationToken, params obje
156155
{
157156
case InitializationType.AsyncObject:
158157
if (_asyncObjectFunc == null)
159-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
158+
throw new NullReferenceException(Constants.InitializationFuncError);
160159

161160
return _asyncObjectFunc(objects).NoSync().GetAwaiter().GetResult();
162161
case InitializationType.AsyncObjectToken:
163162
if (_asyncObjectTokenFunc == null)
164-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
163+
throw new NullReferenceException(Constants.InitializationFuncError);
165164

166165
return _asyncObjectTokenFunc(cancellationToken, objects).NoSync().GetAwaiter().GetResult();
167166
case InitializationType.Async:
168167
if (_asyncFunc == null)
169-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
168+
throw new NullReferenceException(Constants.InitializationFuncError);
170169

171170
return _asyncFunc().NoSync().GetAwaiter().GetResult();
172171
case InitializationType.SyncObject:
173172
if (_objectFunc == null)
174-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
173+
throw new NullReferenceException(Constants.InitializationFuncError);
175174

176175
return _objectFunc(objects);
177176
case InitializationType.SyncObjectToken:
178177
if (_objectTokenFunc == null)
179-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
178+
throw new NullReferenceException(Constants.InitializationFuncError);
180179

181180
return _objectTokenFunc(cancellationToken, objects);
182181
case InitializationType.Sync:
183182
if (_func == null)
184-
throw new NullReferenceException("Initialization func for AsyncSingleton cannot be null");
183+
throw new NullReferenceException(Constants.InitializationFuncError);
185184

186185
return _func();
187186
default:
@@ -190,8 +189,8 @@ private object InitSyncInternal(CancellationToken cancellationToken, params obje
190189
}
191190

192191
public void SetInitialization(Func<object[], ValueTask<object>> func)
193-
{
194-
if (_instance != null)
192+
{
193+
if (_instance is not null)
195194
throw new Exception("Setting the initialization of an AsyncSingleton after it's already has been set is not allowed");
196195

197196
_initializationType = InitializationType.AsyncObject;
@@ -200,7 +199,7 @@ public void SetInitialization(Func<object[], ValueTask<object>> func)
200199

201200
public void SetInitialization(Func<CancellationToken, object[], ValueTask<object>> func)
202201
{
203-
if (_instance != null)
202+
if (_instance is not null)
204203
throw new Exception("Setting the initialization of an AsyncSingleton after it's already has been set is not allowed");
205204

206205
_initializationType = InitializationType.AsyncObjectToken;
@@ -209,7 +208,7 @@ public void SetInitialization(Func<CancellationToken, object[], ValueTask<object
209208

210209
public void SetInitialization(Func<ValueTask<object>> func)
211210
{
212-
if (_instance != null)
211+
if (_instance is not null)
213212
throw new Exception("Setting the initialization of an AsyncSingleton after it's already has been set is not allowed");
214213

215214
_initializationType = InitializationType.Async;
@@ -218,7 +217,7 @@ public void SetInitialization(Func<ValueTask<object>> func)
218217

219218
public void SetInitialization(Func<object[], object> func)
220219
{
221-
if (_instance != null)
220+
if (_instance is not null)
222221
throw new Exception("Setting the initialization of an AsyncSingleton after it's already has been set is not allowed");
223222

224223
_initializationType = InitializationType.SyncObject;
@@ -227,7 +226,7 @@ public void SetInitialization(Func<object[], object> func)
227226

228227
public void SetInitialization(Func<CancellationToken, object[], object> func)
229228
{
230-
if (_instance != null)
229+
if (_instance is not null)
231230
throw new Exception("Setting the initialization of an AsyncSingleton after it's already has been set is not allowed");
232231

233232
_initializationType = InitializationType.SyncObjectToken;
@@ -236,7 +235,7 @@ public void SetInitialization(Func<CancellationToken, object[], object> func)
236235

237236
public void SetInitialization(Func<object> func)
238237
{
239-
if (_instance != null)
238+
if (_instance is not null)
240239
throw new Exception("Setting the initialization of an AsyncSingleton after it's already has been set is not allowed");
241240

242241
_initializationType = InitializationType.Sync;
@@ -250,20 +249,26 @@ public void Dispose()
250249

251250
_disposed = true;
252251

253-
if (_instance == null)
254-
return;
252+
// Capture the instance into a local variable to avoid multiple volatile field accesses.
253+
object? localInstance = _instance;
255254

256-
switch (_instance)
255+
if (localInstance != null)
257256
{
258-
case IDisposable disposable:
257+
// Explicit conditional checks for better predictability in JIT compilation.
258+
if (localInstance is IDisposable disposable)
259+
{
259260
disposable.Dispose();
260-
break;
261-
case IAsyncDisposable asyncDisposable:
261+
}
262+
else if (localInstance is IAsyncDisposable asyncDisposable)
263+
{
264+
// Handle async disposal in a synchronous context.
262265
asyncDisposable.DisposeAsync().NoSync().GetAwaiter().GetResult();
263-
break;
266+
}
267+
268+
// Clear the instance explicitly to allow for garbage collection.
269+
_instance = null;
264270
}
265271

266-
_instance = null;
267272
GC.SuppressFinalize(this);
268273
}
269274

@@ -274,20 +279,28 @@ public async ValueTask DisposeAsync()
274279

275280
_disposed = true;
276281

277-
if (_instance == null)
282+
object? localInstance = _instance;
283+
284+
if (localInstance == null)
285+
{
286+
GC.SuppressFinalize(this);
278287
return;
288+
}
279289

280-
switch (_instance)
290+
// Check for IAsyncDisposable first to avoid unnecessary interface casting
291+
if (localInstance is IAsyncDisposable asyncDisposable)
281292
{
282-
case IAsyncDisposable asyncDisposable:
283-
await asyncDisposable.DisposeAsync().NoSync();
284-
break;
285-
case IDisposable disposable:
286-
disposable.Dispose();
287-
break;
293+
// Await using ConfigureAwait(false) to minimize synchronization context capture
294+
await asyncDisposable.DisposeAsync().NoSync();
295+
}
296+
else if (localInstance is IDisposable disposable)
297+
{
298+
disposable.Dispose();
288299
}
289300

301+
// Clear the instance reference explicitly to assist GC
290302
_instance = null;
303+
291304
GC.SuppressFinalize(this);
292305
}
293306
}

0 commit comments

Comments
 (0)