Skip to content

Commit 802b88e

Browse files
authored
Use the NonCapturingTimer helper (#25205)
1 parent cd61ed7 commit 802b88e

File tree

3 files changed

+15
-31
lines changed

3 files changed

+15
-31
lines changed

src/SignalR/clients/csharp/Client.Core/src/Microsoft.AspNetCore.SignalR.Client.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
<ItemGroup>
1919
<Compile Include="$(SharedSourceRoot)ValueTaskExtensions\**\*.cs" />
20+
<Compile Include="$(SharedSourceRoot)NonCapturingTimer\*.cs" />
2021
</ItemGroup>
2122

2223
<ItemGroup>

src/SignalR/common/Http.Connections/src/Microsoft.AspNetCore.Http.Connections.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<Compile Include="$(SharedSourceRoot)SecurityHelper\**\*.cs" />
2424
<Compile Include="$(SharedSourceRoot)WebEncoders\**\*.cs" />
2525
<Compile Include="$(SharedSourceRoot)ValueTaskExtensions\**\*.cs" />
26+
<Compile Include="$(SharedSourceRoot)NonCapturingTimer\*.cs" />
2627
</ItemGroup>
2728

2829
<ItemGroup>

src/SignalR/common/Shared/TimerAwaitable.cs

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Runtime.CompilerServices;
88
using System.Threading;
99
using System.Threading.Tasks;
10+
using Microsoft.Extensions.Internal;
1011

1112
namespace Microsoft.AspNetCore.Internal
1213
{
@@ -19,9 +20,9 @@ internal class TimerAwaitable : IDisposable, ICriticalNotifyCompletion
1920
private readonly TimeSpan _period;
2021

2122
private readonly TimeSpan _dueTime;
23+
private readonly object _lockObj = new object();
2224
private bool _disposed;
2325
private bool _running = true;
24-
private object _lockObj = new object();
2526

2627
public TimerAwaitable(TimeSpan dueTime, TimeSpan period)
2728
{
@@ -42,39 +43,20 @@ public void Start()
4243

4344
if (_timer == null)
4445
{
45-
// Don't capture the current ExecutionContext and its AsyncLocals onto the timer
46-
bool restoreFlow = false;
47-
try
46+
// This fixes the cycle by using a WeakReference to the state object. The object graph now looks like this:
47+
// Timer -> TimerHolder -> TimerQueueTimer -> WeakReference<TimerAwaitable> -> Timer -> ...
48+
// If TimerAwaitable falls out of scope, the timer should be released.
49+
_timer = NonCapturingTimer.Create(state =>
4850
{
49-
if (!ExecutionContext.IsFlowSuppressed())
51+
var weakRef = (WeakReference<TimerAwaitable>)state!;
52+
if (weakRef.TryGetTarget(out var thisRef))
5053
{
51-
ExecutionContext.SuppressFlow();
52-
restoreFlow = true;
54+
thisRef.Tick();
5355
}
54-
55-
// This fixes the cycle by using a WeakReference to the state object. The object graph now looks like this:
56-
// Timer -> TimerHolder -> TimerQueueTimer -> WeakReference<TimerAwaitable> -> Timer -> ...
57-
// If TimerAwaitable falls out of scope, the timer should be released.
58-
_timer = new Timer(state =>
59-
{
60-
var weakRef = (WeakReference<TimerAwaitable>)state!;
61-
if (weakRef.TryGetTarget(out var thisRef))
62-
{
63-
thisRef.Tick();
64-
}
65-
},
66-
new WeakReference<TimerAwaitable>(this),
67-
_dueTime,
68-
_period);
69-
}
70-
finally
71-
{
72-
// Restore the current ExecutionContext
73-
if (restoreFlow)
74-
{
75-
ExecutionContext.RestoreFlow();
76-
}
77-
}
56+
},
57+
state: new WeakReference<TimerAwaitable>(this),
58+
dueTime: _dueTime,
59+
period: _period);
7860
}
7961
}
8062
}

0 commit comments

Comments
 (0)