瀏覽代碼

Avoid some closure allocations and enable delegate caching in the Concurrency namespace. (#500)

Daniel C. Weber 7 年之前
父節點
當前提交
93bf9b5e42

+ 11 - 9
Rx.NET/Source/src/System.Reactive/Concurrency/CatchScheduler.cs

@@ -76,16 +76,18 @@ namespace System.Reactive.Concurrency
 
             public IDisposable ScheduleLongRunning<TState>(TState state, Action<TState, ICancelable> action)
             {
-                return _scheduler.ScheduleLongRunning(state, (state_, cancel) =>
-                {
-                    try
-                    {
-                        action(state_, cancel);
-                    }
-                    catch (TException exception) when (_handler(exception))
+                return _scheduler.ScheduleLongRunning(
+                    (scheduler: this, action, state), 
+                    (tuple, cancel) =>
                     {
-                    }
-                });
+                        try
+                        {
+                            tuple.action(tuple.state, cancel);
+                        }
+                        catch (TException exception) when (tuple.scheduler._handler(exception))
+                        {
+                        }
+                    });
             }
         }
 

+ 50 - 18
Rx.NET/Source/src/System.Reactive/Concurrency/ConcurrencyAbstractionLayerImpl.cs

@@ -16,6 +16,18 @@ namespace System.Reactive.Concurrency
     //
     internal class /*Default*/ConcurrencyAbstractionLayerImpl : IConcurrencyAbstractionLayer
     {
+        private sealed class WorkItem
+        {
+            public WorkItem(Action<object> action, object state)
+            {
+                this.Action = action;
+                this.State = state;
+            }
+
+            public Action<object> Action { get; }
+            public object State { get; }
+        }
+
         public IDisposable StartTimer(Action<object> action, object state, TimeSpan dueTime) => new Timer(action, state, Normalize(dueTime));
 
         public IDisposable StartPeriodicTimer(Action action, TimeSpan period)
@@ -39,7 +51,13 @@ namespace System.Reactive.Concurrency
 
         public IDisposable QueueUserWorkItem(Action<object> action, object state)
         {
-            System.Threading.ThreadPool.QueueUserWorkItem(_ => action(_), state);
+            System.Threading.ThreadPool.QueueUserWorkItem(itemObject =>
+            {
+                var item = (WorkItem)itemObject;
+
+                item.Action(item.State);
+            }, new WorkItem(action, state));
+
             return Disposable.Empty;
         }
 
@@ -51,10 +69,12 @@ namespace System.Reactive.Concurrency
 
         public void StartThread(Action<object> action, object state)
         {
-            new Thread(() =>
+            new Thread(itemObject =>
             {
-                action(state);
-            }) { IsBackground = true }.Start();
+                var item = (WorkItem)itemObject;
+
+                item.Action(item.State);
+            }) { IsBackground = true }.Start(new WorkItem(action, state));
         }
 
         private static TimeSpan Normalize(TimeSpan dueTime) => dueTime < TimeSpan.Zero ? TimeSpan.Zero : dueTime;
@@ -132,11 +152,13 @@ namespace System.Reactive.Concurrency
 
         private sealed class Timer : IDisposable
         {
+            private object _state;
             private Action<object> _action;
             private volatile System.Threading.Timer _timer;
 
             public Timer(Action<object> action, object state, TimeSpan dueTime)
             {
+                _state = state;
                 _action = action;
 
                 // Don't want the spin wait in Tick to get stuck if this thread gets aborted.
@@ -144,23 +166,25 @@ namespace System.Reactive.Concurrency
                 finally
                 {
                     //
-                    // Rooting of the timer happens through the this.Tick delegate's target object,
+                    // Rooting of the timer happens through the Timer's state
                     // which is the current instance and has a field to store the Timer instance.
                     //
-                    _timer = new System.Threading.Timer(Tick, state, dueTime, TimeSpan.FromMilliseconds(System.Threading.Timeout.Infinite));
+                    _timer = new System.Threading.Timer(_ => Tick(_), this, dueTime, TimeSpan.FromMilliseconds(System.Threading.Timeout.Infinite));
                 }
             }
 
-            private void Tick(object state)
+            private static void Tick(object state)
             {
+                var timer = (Timer) state;
+
                 try
                 {
-                    _action(state);
+                    timer._action(timer._state);
                 }
                 finally
                 {
-                    SpinWait.SpinUntil(IsTimerAssigned);
-                    Dispose();
+                    SpinWait.SpinUntil(timer.IsTimerAssigned);
+                    timer.Dispose();
                 }
             }
 
@@ -173,6 +197,7 @@ namespace System.Reactive.Concurrency
                 {
                     _action = Stubs<object>.Ignore;
                     _timer = TimerStubs.Never;
+                    _state = null;
 
                     timer.Dispose();
                 }
@@ -189,13 +214,18 @@ namespace System.Reactive.Concurrency
                 _action = action;
 
                 //
-                // Rooting of the timer happens through the this.Tick delegate's target object,
+                // Rooting of the timer happens through the timer's state
                 // which is the current instance and has a field to store the Timer instance.
                 //
-                _timer = new System.Threading.Timer(Tick, null, period, period);
+                _timer = new System.Threading.Timer(_ => Tick(_), this, period, period);
             }
 
-            private void Tick(object state) => _action();
+            private static void Tick(object state)
+            {
+                var timer = (PeriodicTimer)state;
+
+                timer._action();
+            }
 
             public void Dispose()
             {
@@ -219,19 +249,21 @@ namespace System.Reactive.Concurrency
             {
                 _action = action;
                 
-                new System.Threading.Thread(Loop)
+                new System.Threading.Thread(_ => Loop(_))
                 {
                     Name = "Rx-FastPeriodicTimer",
                     IsBackground = true
                 }
-                .Start();
+                .Start(this);
             }
             
-            private void Loop()
+            private static void Loop(object threadParam)
             {
-                while (!disposed)
+                var timer = (FastPeriodicTimer)threadParam;
+
+                while (!timer.disposed)
                 {
-                    _action();
+                    timer._action();
                 }
             }
 

+ 48 - 24
Rx.NET/Source/src/System.Reactive/Concurrency/DefaultScheduler.cs

@@ -12,6 +12,43 @@ namespace System.Reactive.Concurrency
     /// <seealso cref="Scheduler.Default">Singleton instance of this type exposed through this static property.</seealso>
     public sealed class DefaultScheduler : LocalScheduler, ISchedulerPeriodic
     {
+        private sealed class UserWorkItem<TState> : IDisposable
+        {
+            private IDisposable _cancelRunDisposable;
+            private IDisposable _cancelQueueDisposable;
+
+            private readonly TState _state;
+            private readonly IScheduler _scheduler;
+            private readonly Func<IScheduler, TState, IDisposable> _action;
+
+            public UserWorkItem(IScheduler scheduler, TState state, Func<IScheduler, TState, IDisposable> action)
+            {
+                _state = state;
+                _action = action;
+                _scheduler = scheduler;
+            }
+
+            public void Run()
+            {
+                if (!Disposable.GetIsDisposed(ref _cancelRunDisposable))
+                {
+                    Disposable.TrySetSingle(ref _cancelRunDisposable, _action(_scheduler, _state));
+                }
+            }
+
+            public IDisposable CancelQueueDisposable
+            {
+                get => Disposable.GetValue(ref _cancelQueueDisposable);
+                set => Disposable.TrySetSingle(ref _cancelQueueDisposable, value);
+            }
+
+            public void Dispose()
+            {
+                Disposable.TryDispose(ref _cancelQueueDisposable);
+                Disposable.TryDispose(ref _cancelRunDisposable);
+            }
+        }
+
         private static readonly Lazy<DefaultScheduler> s_instance = new Lazy<DefaultScheduler>(() => new DefaultScheduler());
         private static IConcurrencyAbstractionLayer s_cal = ConcurrencyAbstractionLayer.Current;
 
@@ -37,20 +74,13 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            var d = new SingleAssignmentDisposable();
+            var workItem = new UserWorkItem<TState>(this, state, action);
 
-            var cancel = s_cal.QueueUserWorkItem(_ =>
-            {
-                if (!d.IsDisposed)
-                {
-                    d.Disposable = action(this, state);
-                }
-            }, null);
+            workItem.CancelQueueDisposable = s_cal.QueueUserWorkItem(
+                closureWorkItem => ((UserWorkItem<TState>)closureWorkItem).Run(),
+                workItem);
 
-            return StableCompositeDisposable.Create(
-                d,
-                cancel
-            );
+            return workItem;
         }
 
         /// <summary>
@@ -71,20 +101,14 @@ namespace System.Reactive.Concurrency
             if (dt.Ticks == 0)
                 return Schedule(state, action);
 
-            var d = new SingleAssignmentDisposable();
+            var workItem = new UserWorkItem<TState>(this, state, action);
 
-            var cancel = s_cal.StartTimer(_ =>
-            {
-                if (!d.IsDisposed)
-                {
-                    d.Disposable = action(this, state);
-                }
-            }, null, dt);
+            workItem.CancelQueueDisposable = s_cal.StartTimer(
+                closureWorkItem => ((UserWorkItem<TState>)closureWorkItem).Run(),
+                workItem,
+                dt);
 
-            return StableCompositeDisposable.Create(
-                d,
-                cancel
-            );
+            return workItem;
         }
 
         /// <summary>

+ 13 - 13
Rx.NET/Source/src/System.Reactive/Concurrency/Scheduler.Async.cs

@@ -159,7 +159,7 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return ScheduleAsync_(scheduler, default(object), (self, o, ct) => action(self, ct));
+            return ScheduleAsync_(scheduler, action, (self, closureAction, ct) => closureAction(self, ct));
         }
 
         /// <summary>
@@ -176,7 +176,7 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return ScheduleAsync_(scheduler, default(object), (self, o, ct) => action(self, ct));
+            return ScheduleAsync_(scheduler, action, (self, closureAction, ct) => closureAction(self, ct));
         }
 
         /// <summary>
@@ -234,7 +234,7 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return ScheduleAsync_(scheduler, default(object), dueTime, (self, o, ct) => action(self, ct));
+            return ScheduleAsync_(scheduler, action, dueTime, (self, closureAction, ct) => closureAction(self, ct));
         }
 
         /// <summary>
@@ -252,7 +252,7 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return ScheduleAsync_(scheduler, default(object), dueTime, (self, o, ct) => action(self, ct));
+            return ScheduleAsync_(scheduler, action, dueTime, (self, closureAction, ct) => closureAction(self, ct));
         }
 
         /// <summary>
@@ -310,7 +310,7 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return ScheduleAsync_(scheduler, default(object), dueTime, (self, o, ct) => action(self, ct));
+            return ScheduleAsync_(scheduler, action, dueTime, (self, closureAction, ct) => closureAction(self, ct));
         }
 
         /// <summary>
@@ -328,37 +328,37 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return ScheduleAsync_(scheduler, default(object), dueTime, (self, o, ct) => action(self, ct));
+            return ScheduleAsync_(scheduler, action, dueTime, (self, closureAction, ct) => closureAction(self, ct));
         }
 
         private static IDisposable ScheduleAsync_<TState>(IScheduler scheduler, TState state, Func<IScheduler, TState, CancellationToken, Task> action)
         {
-            return scheduler.Schedule(state, (self, s) => InvokeAsync(self, s, action));
+            return scheduler.Schedule((state, action), (self, t) => InvokeAsync(self, t.state, t.action));
         }
 
         private static IDisposable ScheduleAsync_<TState>(IScheduler scheduler, TState state, Func<IScheduler, TState, CancellationToken, Task<IDisposable>> action)
         {
-            return scheduler.Schedule(state, (self, s) => InvokeAsync(self, s, action));
+            return scheduler.Schedule((state, action), (self, t) => InvokeAsync(self, t.state, t.action));
         }
 
         private static IDisposable ScheduleAsync_<TState>(IScheduler scheduler, TState state, TimeSpan dueTime, Func<IScheduler, TState, CancellationToken, Task> action)
         {
-            return scheduler.Schedule(state, dueTime, (self, s) => InvokeAsync(self, s, action));
+            return scheduler.Schedule((state, action), dueTime, (self, t) => InvokeAsync(self, t.state, t.action));
         }
 
         private static IDisposable ScheduleAsync_<TState>(IScheduler scheduler, TState state, TimeSpan dueTime, Func<IScheduler, TState, CancellationToken, Task<IDisposable>> action)
         {
-            return scheduler.Schedule(state, dueTime, (self, s) => InvokeAsync(self, s, action));
+            return scheduler.Schedule((state, action), dueTime, (self, t) => InvokeAsync(self, t.state, t.action));
         }
 
         private static IDisposable ScheduleAsync_<TState>(IScheduler scheduler, TState state, DateTimeOffset dueTime, Func<IScheduler, TState, CancellationToken, Task> action)
         {
-            return scheduler.Schedule(state, dueTime, (self, s) => InvokeAsync(self, s, action));
+            return scheduler.Schedule((state, action), dueTime, (self, t) => InvokeAsync(self, t.state, t.action));
         }
 
         private static IDisposable ScheduleAsync_<TState>(IScheduler scheduler, TState state, DateTimeOffset dueTime, Func<IScheduler, TState, CancellationToken, Task<IDisposable>> action)
         {
-            return scheduler.Schedule(state, dueTime, (self, s) => InvokeAsync(self, s, action));
+            return scheduler.Schedule((state, action), dueTime, (self, t) => InvokeAsync(self, t.state, t.action));
         }
 
         private static IDisposable InvokeAsync<TState>(IScheduler self, TState s, Func<IScheduler, TState, CancellationToken, Task<IDisposable>> action)
@@ -384,7 +384,7 @@ namespace System.Reactive.Concurrency
 
         private static IDisposable InvokeAsync<TState>(IScheduler self, TState s, Func<IScheduler, TState, CancellationToken, Task> action)
         {
-            return InvokeAsync(self, s, (self_, state, ct) => action(self_, state, ct).ContinueWith(_ => Disposable.Empty));
+            return InvokeAsync(self, (action, state: s), (self_, t, ct) => t.action(self_, t.state, ct).ContinueWith(_ => Disposable.Empty));
         }
 
         private static CancellationToken GetCancellationToken(this IScheduler scheduler)

+ 12 - 27
Rx.NET/Source/src/System.Reactive/Concurrency/Scheduler.Recursive.cs

@@ -41,18 +41,16 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return scheduler.Schedule(new Pair<TState, Action<TState, Action<TState>>> { First = state, Second = action }, InvokeRec1);
+            return scheduler.Schedule((state, action), (s, p) => InvokeRec1(s, p));
         }
 
-        private static IDisposable InvokeRec1<TState>(IScheduler scheduler, Pair<TState, Action<TState, Action<TState>>> pair)
+        private static IDisposable InvokeRec1<TState>(IScheduler scheduler, (TState state, Action<TState, Action<TState>> action) tuple)
         {
             var group = new CompositeDisposable(1);
             var gate = new object();
-            var state = pair.First;
-            var action = pair.Second;
 
             Action<TState> recursiveAction = null;
-            recursiveAction = state1 => action(state1, state2 =>
+            recursiveAction = state1 => tuple.action(state1, state2 =>
             {
                 var isAdded = false;
                 var isDone = false;
@@ -84,7 +82,7 @@ namespace System.Reactive.Concurrency
                 }
             });
 
-            recursiveAction(state);
+            recursiveAction(tuple.state);
 
             return group;
         }
@@ -124,18 +122,16 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return scheduler.Schedule(new Pair<TState, Action<TState, Action<TState, TimeSpan>>> { First = state, Second = action }, dueTime, InvokeRec2);
+            return scheduler.Schedule((state, action), dueTime, (s, p) => InvokeRec2(s, p));
         }
 
-        private static IDisposable InvokeRec2<TState>(IScheduler scheduler, Pair<TState, Action<TState, Action<TState, TimeSpan>>> pair)
+        private static IDisposable InvokeRec2<TState>(IScheduler scheduler, (TState state, Action<TState, Action<TState, TimeSpan>> action) tuple)
         {
             var group = new CompositeDisposable(1);
             var gate = new object();
-            var state = pair.First;
-            var action = pair.Second;
 
             Action<TState> recursiveAction = null;
-            recursiveAction = state1 => action(state1, (state2, dueTime1) =>
+            recursiveAction = state1 => tuple.action(state1, (state2, dueTime1) =>
             {
                 var isAdded = false;
                 var isDone = false;
@@ -167,7 +163,7 @@ namespace System.Reactive.Concurrency
                 }
             });
 
-            recursiveAction(state);
+            recursiveAction(tuple.state);
 
             return group;
         }
@@ -207,18 +203,16 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return scheduler.Schedule(new Pair<TState, Action<TState, Action<TState, DateTimeOffset>>> { First = state, Second = action }, dueTime, InvokeRec3);
+            return scheduler.Schedule((state, action), dueTime, (s, p) => InvokeRec3(s, p));
         }
 
-        private static IDisposable InvokeRec3<TState>(IScheduler scheduler, Pair<TState, Action<TState, Action<TState, DateTimeOffset>>> pair)
+        private static IDisposable InvokeRec3<TState>(IScheduler scheduler, (TState state, Action<TState, Action<TState, DateTimeOffset>> action) tuple)
         {
             var group = new CompositeDisposable(1);
             var gate = new object();
-            var state = pair.First;
-            var action = pair.Second;
 
             Action<TState> recursiveAction = null;
-            recursiveAction = state1 => action(state1, (state2, dueTime1) =>
+            recursiveAction = state1 => tuple.action(state1, (state2, dueTime1) =>
             {
                 var isAdded = false;
                 var isDone = false;
@@ -250,18 +244,9 @@ namespace System.Reactive.Concurrency
                 }
             });
 
-            recursiveAction(state);
+            recursiveAction(tuple.state);
 
             return group;
         }
-
-#if !NO_SERIALIZABLE
-        [Serializable]
-#endif
-        private struct Pair<T1, T2>
-        {
-            public T1 First;
-            public T2 Second;
-        }
     }
 }

+ 1 - 1
Rx.NET/Source/src/System.Reactive/Concurrency/Scheduler.Services.Emulation.cs

@@ -60,7 +60,7 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return SchedulePeriodic_(scheduler, state, period, state_ => { action(state_); return state_; });
+            return SchedulePeriodic_(scheduler, (state, action), period, t => { t.action(t.state); return t; });
         }
 
         /// <summary>

+ 12 - 3
Rx.NET/Source/src/System.Reactive/Concurrency/Scheduler.Simple.cs

@@ -22,7 +22,14 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return scheduler.Schedule(action, Invoke);
+            // Surprisingly, passing the method group of Invoke will create a fresh
+            // delegate each an every time, although it's static, while an anonymous
+            // lambda without the need of a closure will be cached.
+            // Once Roslyn supports caching delegates for method groups,
+            // the anonymous lambda can be replaced by the method group again. Until then,
+            // to avoid the repetition of code, the call to Invoke is left intact.
+            // Watch https://github.com/dotnet/roslyn/issues/5835
+            return scheduler.Schedule(action, (s, a) => Invoke(s, a));
         }
 
         /// <summary>
@@ -64,7 +71,8 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return scheduler.Schedule(action, dueTime, Invoke);
+            // See note above.
+            return scheduler.Schedule(action, dueTime, (s, a) => Invoke(s, a));
         }
 
         /// <summary>
@@ -82,7 +90,8 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return scheduler.Schedule(action, dueTime, Invoke);
+            // See note above.
+            return scheduler.Schedule(action, dueTime, (s, a) => Invoke(s, a));
         }
 
         /// <summary>

+ 10 - 12
Rx.NET/Source/src/System.Reactive/Concurrency/SynchronizationContextScheduler.cs

@@ -57,22 +57,20 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            var d = new SingleAssignmentDisposable();
-
             if (!_alwaysPost && _context == SynchronizationContext.Current)
             {
-                d.Disposable = action(this, state);
+                return action(this, state);
             }
-            else
+
+            var d = new SingleAssignmentDisposable();
+
+            _context.PostWithStartComplete(() =>
             {
-                _context.PostWithStartComplete(() =>
+                if (!d.IsDisposed)
                 {
-                    if (!d.IsDisposed)
-                    {
-                        d.Disposable = action(this, state);
-                    }
-                });
-            }
+                    d.Disposable = action(this, state);
+                }
+            });
 
             return d;
         }
@@ -97,7 +95,7 @@ namespace System.Reactive.Concurrency
                 return Schedule(state, action);
             }
 
-            return DefaultScheduler.Instance.Schedule(state, dt, (_, state1) => Schedule(state1, action));
+            return DefaultScheduler.Instance.Schedule((scheduler: this, action, state), dt, (_, tuple) => tuple.scheduler.Schedule(tuple.state, tuple.action));
         }
     }
 }

+ 49 - 20
Rx.NET/Source/src/System.Reactive/Concurrency/ThreadPoolScheduler.cs

@@ -15,6 +15,35 @@ namespace System.Reactive.Concurrency
     /// <seealso cref="ThreadPoolScheduler.Instance">Singleton instance of this type exposed through this static property.</seealso>
     public sealed class ThreadPoolScheduler : LocalScheduler, ISchedulerLongRunning, ISchedulerPeriodic
     {
+        private sealed class UserWorkItem<TState> : IDisposable
+        {
+            private IDisposable _cancelRunDisposable;
+
+            private readonly TState _state;
+            private readonly IScheduler _scheduler;
+            private readonly Func<IScheduler, TState, IDisposable> _action;
+
+            public UserWorkItem(IScheduler scheduler, TState state, Func<IScheduler, TState, IDisposable> action)
+            {
+                _state = state;
+                _action = action;
+                _scheduler = scheduler;
+            }
+
+            public void Run()
+            {
+                if (!Disposable.GetIsDisposed(ref _cancelRunDisposable))
+                {
+                    Disposable.TrySetSingle(ref _cancelRunDisposable, _action(_scheduler, _state));
+                }
+            }
+
+            public void Dispose()
+            {
+                Disposable.TryDispose(ref _cancelRunDisposable);
+            }
+        }
+
         private static readonly Lazy<ThreadPoolScheduler> s_instance = new Lazy<ThreadPoolScheduler>(() => new ThreadPoolScheduler());
         private static readonly Lazy<NewThreadScheduler> s_newBackgroundThread = new Lazy<NewThreadScheduler>(() => new NewThreadScheduler(action => new Thread(action) { IsBackground = true }));
 
@@ -40,17 +69,13 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            var d = new SingleAssignmentDisposable();
+            var workItem = new UserWorkItem<TState>(this, state, action);
 
-            ThreadPool.QueueUserWorkItem(_ =>
-            {
-                if (!d.IsDisposed)
-                {
-                    d.Disposable = action(this, state);
-                }
-            }, null);
+            ThreadPool.QueueUserWorkItem(
+                closureWorkItem => ((UserWorkItem<TState>)closureWorkItem).Run(), 
+                workItem);
 
-            return d;
+            return workItem;
         }
 
         /// <summary>
@@ -144,15 +169,17 @@ namespace System.Reactive.Concurrency
                 _state = state;
                 _action = action;
 
-                ThreadPool.QueueUserWorkItem(Tick, null);
+                ThreadPool.QueueUserWorkItem(_ => Tick(_), this);   // Replace with method group as soon as Roslyn will cache the delegate then.
             }
 
-            private void Tick(object state)
+            private static void Tick(object state)
             {
-                if (!_disposed)
+                var timer = (FastPeriodicTimer<TState>)state;
+
+                if (!timer._disposed)
                 {
-                    _state = _action(_state);
-                    ThreadPool.QueueUserWorkItem(Tick, null);
+                    timer._state = timer._action(timer._state);
+                    ThreadPool.QueueUserWorkItem(_ => Tick(_), timer);
                 }
             }
 
@@ -192,23 +219,25 @@ namespace System.Reactive.Concurrency
                 finally
                 {
                     //
-                    // Rooting of the timer happens through the this.Tick delegate's target object,
+                    // Rooting of the timer happens through the passed state,
                     // which is the current instance and has a field to store the Timer instance.
                     //
-                    _timer = new System.Threading.Timer(this.Tick, null, dueTime, TimeSpan.FromMilliseconds(System.Threading.Timeout.Infinite));
+                    _timer = new System.Threading.Timer(_ => Tick(_) /* Don't convert to method group until Roslyn catches up */, this, dueTime, TimeSpan.FromMilliseconds(System.Threading.Timeout.Infinite));
                 }
             }
 
-            private void Tick(object state)
+            private static void Tick(object state)
             {
+                var timer = (Timer<TState>)state;
+
                 try
                 {
-                    _disposable.Disposable = _action(_parent, _state);
+                    timer._disposable.Disposable = timer._action(timer._parent, timer._state);
                 }
                 finally
                 {
-                    SpinWait.SpinUntil(IsTimerAssigned);
-                    Stop();
+                    SpinWait.SpinUntil(timer.IsTimerAssigned);
+                    timer.Stop();
                 }
             }
 

+ 6 - 2
Rx.NET/Source/src/System.Reactive/Concurrency/VirtualTimeScheduler.Extensions.cs

@@ -29,7 +29,11 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return scheduler.ScheduleRelative(action, dueTime, Invoke);
+            // As stated in Scheduler.Simple.cs,
+            // an anonymous delegate will allow delegate caching.
+            // Watch https://github.com/dotnet/roslyn/issues/5835 for compiler
+            // support for caching delegates from method groups.
+            return scheduler.ScheduleRelative(action, dueTime, (s, a) => Invoke(s, a));
         }
 
         /// <summary>
@@ -50,7 +54,7 @@ namespace System.Reactive.Concurrency
             if (action == null)
                 throw new ArgumentNullException(nameof(action));
 
-            return scheduler.ScheduleAbsolute(action, dueTime, Invoke);
+            return scheduler.ScheduleAbsolute(action, dueTime, (s, a) => Invoke(s, a));
         }
 
         private static IDisposable Invoke(IScheduler scheduler, Action action)