Skip to content

Commit 7551d1a

Browse files
authored
Use the stateful overload of AsyncLock in some places to save allocation of closures and allow delegate caching. (#583)
1 parent b6058c0 commit 7551d1a

File tree

9 files changed

+171
-120
lines changed

9 files changed

+171
-120
lines changed

Rx.NET/Source/src/System.Reactive/Concurrency/DefaultScheduler.cs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,25 +128,40 @@ public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<
128128
if (action == null)
129129
throw new ArgumentNullException(nameof(action));
130130

131-
var state1 = state;
132-
var gate = new AsyncLock();
131+
return new PeriodicallyScheduledWorkItem<TState>(state, period, action);
132+
}
133+
134+
private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable
135+
{
136+
private TState _state;
137+
private Func<TState, TState> _action;
138+
private readonly IDisposable _cancel;
139+
private readonly AsyncLock _gate = new AsyncLock();
133140

134-
var cancel = s_cal.StartPeriodicTimer(() =>
141+
public PeriodicallyScheduledWorkItem(TState state, TimeSpan period, Func<TState, TState> action)
135142
{
136-
gate.Wait(() =>
137-
{
138-
state1 = action(state1);
139-
});
140-
}, period);
143+
_state = state;
144+
_action = action;
145+
146+
_cancel = s_cal.StartPeriodicTimer(Tick, period);
147+
}
141148

142-
return Disposable.Create(() =>
149+
private void Tick()
143150
{
144-
cancel.Dispose();
145-
gate.Dispose();
146-
action = Stubs<TState>.I;
147-
});
151+
_gate.Wait(
152+
this,
153+
closureWorkItem => closureWorkItem._state = closureWorkItem._action(closureWorkItem._state));
154+
}
155+
156+
public void Dispose()
157+
{
158+
_cancel.Dispose();
159+
_gate.Dispose();
160+
_action = Stubs<TState>.I;
161+
}
148162
}
149163

164+
150165
/// <summary>
151166
/// Discovers scheduler services by interface type.
152167
/// </summary>

Rx.NET/Source/src/System.Reactive/Concurrency/EventLoopScheduler.cs

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -191,52 +191,48 @@ public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<
191191
if (action == null)
192192
throw new ArgumentNullException(nameof(action));
193193

194-
var start = _stopwatch.Elapsed;
195-
var next = start + period;
196-
197-
var state1 = state;
194+
return new PeriodicallyScheduledWorkItem<TState>(this, state, period, action);
195+
}
198196

199-
var td = new TernaryDisposable();
197+
private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable
198+
{
199+
private readonly TimeSpan _period;
200+
private readonly Func<TState, TState> _action;
201+
private readonly EventLoopScheduler _scheduler;
202+
private readonly AsyncLock _gate = new AsyncLock();
200203

201-
var gate = new AsyncLock();
202-
td.Extra = gate;
204+
private TState _state;
205+
private TimeSpan _next;
206+
private IDisposable _task;
203207

204-
var tick = default(Func<IScheduler, object, IDisposable>);
205-
tick = (self_, _) =>
208+
public PeriodicallyScheduledWorkItem(EventLoopScheduler scheduler, TState state, TimeSpan period, Func<TState, TState> action)
206209
{
207-
next += period;
210+
_state = state;
211+
_period = period;
212+
_action = action;
213+
_scheduler = scheduler;
214+
_next = scheduler._stopwatch.Elapsed + period;
208215

209-
td.Next = self_.Schedule(null, next - _stopwatch.Elapsed, tick);
210-
211-
gate.Wait(() =>
212-
{
213-
state1 = action(state1);
214-
});
215-
216-
return Disposable.Empty;
217-
};
216+
Disposable.TrySetSingle(ref _task, scheduler.Schedule(this, _next - scheduler._stopwatch.Elapsed, (_, s) => s.Tick(_)));
217+
}
218218

219-
td.First = Schedule(null, next - _stopwatch.Elapsed, tick);
219+
private IDisposable Tick(IScheduler self)
220+
{
221+
_next += _period;
220222

221-
return td;
222-
}
223+
Disposable.TrySetMultiple(ref _task, self.Schedule(this, _next - _scheduler._stopwatch.Elapsed, (_, s) => s.Tick(_)));
223224

224-
private sealed class TernaryDisposable : IDisposable
225-
{
226-
private IDisposable _task;
227-
private IDisposable _extra;
225+
_gate.Wait(
226+
this,
227+
closureWorkItem => closureWorkItem._state = closureWorkItem._action(closureWorkItem._state));
228228

229-
// If Next was called before this assignment is executed, it won't overwrite
230-
// a more fresh IDisposable task
231-
public IDisposable First { set { Disposable.TrySetSingle(ref _task, value); } }
232-
// It is fine to overwrite the first or previous IDisposable task
233-
public IDisposable Next { set { Disposable.TrySetMultiple(ref _task, value); } }
234-
public IDisposable Extra { set { Disposable.SetSingle(ref _extra, value); } }
229+
return Disposable.Empty;
230+
}
235231

236232
public void Dispose()
237233
{
238234
Disposable.TryDispose(ref _task);
239-
Disposable.TryDispose(ref _extra);
235+
_gate.Dispose();
240236
}
241237
}
242238

Rx.NET/Source/src/System.Reactive/Concurrency/ImmediateScheduler.cs

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,15 @@ public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TSta
7878
asyncLock = new AsyncLock();
7979
}
8080

81-
asyncLock.Wait(() =>
82-
{
83-
if (!m.IsDisposed)
81+
asyncLock.Wait(
82+
(@this: this, m, action, state),
83+
tuple =>
8484
{
85-
m.Disposable = action(this, state);
86-
}
87-
});
85+
if (!m.IsDisposed)
86+
{
87+
tuple.m.Disposable = tuple.action(tuple.@this, tuple.state);
88+
}
89+
});
8890

8991
return m;
9092
}
@@ -113,22 +115,24 @@ private IDisposable ScheduleSlow<TState>(TState state, TimeSpan dueTime, Func<IS
113115
asyncLock = new AsyncLock();
114116
}
115117

116-
asyncLock.Wait(() =>
117-
{
118-
if (!m.IsDisposed)
118+
asyncLock.Wait(
119+
(@this: this, m, state, action, timer, dueTime),
120+
tuple =>
119121
{
120-
var sleep = dueTime - timer.Elapsed;
121-
if (sleep.Ticks > 0)
122-
{
123-
ConcurrencyAbstractionLayer.Current.Sleep(sleep);
124-
}
125-
126-
if (!m.IsDisposed)
122+
if (!tuple.m.IsDisposed)
127123
{
128-
m.Disposable = action(this, state);
124+
var sleep = tuple.dueTime - tuple.timer.Elapsed;
125+
if (sleep.Ticks > 0)
126+
{
127+
ConcurrencyAbstractionLayer.Current.Sleep(sleep);
128+
}
129+
130+
if (!tuple.m.IsDisposed)
131+
{
132+
tuple.m.Disposable = tuple.action(tuple.@this, tuple.state);
133+
}
129134
}
130-
}
131-
});
135+
});
132136

133137
return m;
134138
}

Rx.NET/Source/src/System.Reactive/Concurrency/TaskPoolScheduler.cs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -179,31 +179,54 @@ public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<
179179
if (action == null)
180180
throw new ArgumentNullException(nameof(action));
181181

182-
var cancel = new CancellationDisposable();
182+
return new PeriodicallyScheduledWorkItem<TState>(state, period, action, taskFactory);
183+
}
184+
185+
private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable
186+
{
187+
private TState _state;
188+
189+
private readonly TimeSpan _period;
190+
private readonly TaskFactory _taskFactory;
191+
private readonly Func<TState, TState> _action;
192+
private readonly AsyncLock _gate = new AsyncLock();
193+
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
194+
195+
public PeriodicallyScheduledWorkItem(TState state, TimeSpan period, Func<TState, TState> action, TaskFactory taskFactory)
196+
{
197+
_state = state;
198+
_period = period;
199+
_action = action;
200+
_taskFactory = taskFactory;
201+
202+
MoveNext();
203+
}
183204

184-
var state1 = state;
185-
var gate = new AsyncLock();
205+
public void Dispose()
206+
{
207+
_cts.Cancel();
208+
_gate.Dispose();
209+
}
186210

187-
var moveNext = default(Action);
188-
moveNext = () =>
211+
private void MoveNext()
189212
{
190-
TaskHelpers.Delay(period, cancel.Token).ContinueWith(
191-
_ =>
213+
TaskHelpers.Delay(_period, _cts.Token).ContinueWith(
214+
(_, thisObject) =>
192215
{
193-
moveNext();
216+
var @this = (PeriodicallyScheduledWorkItem<TState>)thisObject;
217+
218+
@this.MoveNext();
194219

195-
gate.Wait(() =>
196-
{
197-
state1 = action(state1);
198-
});
220+
@this._gate.Wait(
221+
@this,
222+
closureThis => closureThis._state = closureThis._action(closureThis._state));
199223
},
200-
CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, taskFactory.Scheduler
224+
this,
225+
CancellationToken.None,
226+
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
227+
_taskFactory.Scheduler
201228
);
202-
};
203-
204-
moveNext();
205-
206-
return StableCompositeDisposable.Create(cancel, gate);
229+
}
207230
}
208231
}
209232
}

Rx.NET/Source/src/System.Reactive/Concurrency/ThreadPoolScheduler.Windows.cs

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -157,26 +157,40 @@ public IDisposable SchedulePeriodic<TState>(TState state, TimeSpan period, Func<
157157
if (action == null)
158158
throw new ArgumentNullException(nameof(action));
159159

160-
var state1 = state;
161-
var gate = new AsyncLock();
160+
return new PeriodicallyScheduledWorkItem<TState>(state, period, action);
161+
}
162162

163-
var res = global::Windows.System.Threading.ThreadPoolTimer.CreatePeriodicTimer(
164-
tpt =>
165-
{
166-
gate.Wait(() =>
167-
{
168-
state1 = action(state1);
169-
});
170-
},
171-
period
172-
);
163+
private sealed class PeriodicallyScheduledWorkItem<TState> : IDisposable
164+
{
165+
private TState _state;
166+
private Func<TState, TState> _action;
167+
168+
private readonly ThreadPoolTimer _timer;
169+
private readonly AsyncLock _gate = new AsyncLock();
173170

174-
return Disposable.Create(() =>
171+
public PeriodicallyScheduledWorkItem(TState state, TimeSpan period, Func<TState, TState> action)
175172
{
176-
res.Cancel();
177-
gate.Dispose();
178-
action = Stubs<TState>.I;
179-
});
173+
_state = state;
174+
_action = action;
175+
176+
_timer = global::Windows.System.Threading.ThreadPoolTimer.CreatePeriodicTimer(
177+
Tick,
178+
period);
179+
}
180+
181+
private void Tick(ThreadPoolTimer timer)
182+
{
183+
_gate.Wait(
184+
this,
185+
@this => @this._state = @this._action(@this._state));
186+
}
187+
188+
public void Dispose()
189+
{
190+
_timer.Cancel();
191+
_gate.Dispose();
192+
_action = Stubs<TState>.I;
193+
}
180194
}
181195
}
182196
}

Rx.NET/Source/src/System.Reactive/Concurrency/ThreadPoolScheduler.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,12 @@ public PeriodicTimer(TState state, TimeSpan period, Func<TState, TState> action)
284284

285285
private void Tick(object state)
286286
{
287-
_gate.Wait(() =>
288-
{
289-
_state = _action(_state);
290-
});
287+
_gate.Wait(
288+
this,
289+
@this =>
290+
{
291+
@this._state = @this._action(@this._state);
292+
});
291293
}
292294

293295
public void Dispose()

Rx.NET/Source/src/System.Reactive/Internal/AsyncLockObserver.cs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,23 @@ public AsyncLockObserver(IObserver<T> observer, AsyncLock gate)
1919

2020
protected override void OnNextCore(T value)
2121
{
22-
_gate.Wait(() =>
23-
{
24-
_observer.OnNext(value);
25-
});
22+
_gate.Wait(
23+
(_observer, value),
24+
tuple => tuple._observer.OnNext(tuple.value));
2625
}
2726

2827
protected override void OnErrorCore(Exception exception)
2928
{
30-
_gate.Wait(() =>
31-
{
32-
_observer.OnError(exception);
33-
});
29+
_gate.Wait(
30+
(_observer, exception),
31+
tuple => tuple._observer.OnError(tuple.exception));
3432
}
3533

3634
protected override void OnCompletedCore()
3735
{
38-
_gate.Wait(() =>
39-
{
40-
_observer.OnCompleted();
41-
});
36+
_gate.Wait(
37+
_observer,
38+
closureObserver => closureObserver.OnCompleted());
4239
}
4340
}
4441
}

Rx.NET/Source/src/System.Reactive/Linq/Observable/Buffer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ public override void Run(IObservable<TSource> source)
520520

521521
base.Run(source);
522522

523-
_bufferGate.Wait(CreateBufferClose);
523+
_bufferGate.Wait(this, @this => @this.CreateBufferClose());
524524
}
525525

526526
protected override void Dispose(bool disposing)
@@ -564,7 +564,7 @@ private void CloseBuffer(IDisposable closingSubscription)
564564
ForwardOnNext(res);
565565
}
566566

567-
_bufferGate.Wait(CreateBufferClose);
567+
_bufferGate.Wait(this, @this => @this.CreateBufferClose());
568568
}
569569

570570
private sealed class BufferClosingObserver : IObserver<TBufferClosing>

0 commit comments

Comments
 (0)