11using System ;
22using System . Threading ;
33using System . Threading . Tasks ;
4+ using Open . Threading ;
45
56namespace Open . Threading
67{
@@ -21,7 +22,7 @@ public static ActionRunner Create(Action action, TaskScheduler scheduler = null)
2122
2223 public static ActionRunner Create < T > ( Func < T > action , TaskScheduler scheduler = null )
2324 {
24- return new ActionRunner ( ( ) => { action ( ) ; } , scheduler ) ;
25+ return new ActionRunner ( ( ) => { action ( ) ; } , scheduler ) ;
2526 }
2627
2728 Action _action ;
@@ -69,7 +70,13 @@ public Exception LastFault
6970
7071 public bool Cancel ( bool onlyIfNotRunning )
7172 {
72- return _task ? . Value . Cancel ( onlyIfNotRunning ) ?? false ;
73+ var t = _task ;
74+ if ( t ? . Cancel ( onlyIfNotRunning ) ?? false )
75+ {
76+ Interlocked . CompareExchange ( ref _task , null , t ) ;
77+ return true ;
78+ }
79+ return false ;
7380 }
7481
7582 public bool Cancel ( )
@@ -95,7 +102,7 @@ public bool IsScheduled
95102 {
96103 get
97104 {
98- return _task ? . Value . IsActive ( ) ?? false ;
105+ return _task ? . IsActive ( ) ?? false ;
99106 }
100107 }
101108
@@ -107,14 +114,14 @@ public void RunSynchronously()
107114 GetAction ( ) . Invoke ( ) ;
108115 }
109116
110- // Use a Lazy to ensure only 1 time initialization.
111- Lazy < CancellableTask > _task ;
117+ readonly object _taskLock = new object ( ) ;
118+ CancellableTask _task ;
112119 CancellableTask Prepare ( TimeSpan delay )
113120 {
114121 LastStart = DateTime . Now ;
115122 var task = CancellableTask . Start ( GetAction ( ) , delay , _scheduler ) ;
116123 task
117- . OnFaulted ( ex=>
124+ . OnFaulted ( ex =>
118125 {
119126 LastFault = ex ;
120127 } )
@@ -123,8 +130,9 @@ CancellableTask Prepare(TimeSpan delay)
123130 LastComplete = DateTime . Now ;
124131 Interlocked . Increment ( ref _count ) ;
125132 } )
126- . ContinueWith ( t => {
127- Interlocked . Exchange ( ref _task , null ) ;
133+ . ContinueWith ( t =>
134+ {
135+ Interlocked . CompareExchange ( ref _task , null , task ) ;
128136 } ) ;
129137 return task ;
130138 }
@@ -136,9 +144,22 @@ public CancellableTask Run()
136144
137145 public CancellableTask Defer ( TimeSpan delay , bool clearSchedule = true )
138146 {
139- if ( clearSchedule ) Cancel ( true ) ; // Don't cancel defered if already running.
140- var task = LazyInitializer . EnsureInitialized (
141- ref _task , ( ) => new Lazy < CancellableTask > ( ( ) => Prepare ( delay ) ) ) . Value ;
147+ if ( clearSchedule )
148+ {
149+ Cancel ( true ) ; // Don't cancel defered if already running.
150+ }
151+ CancellableTask task = null ;
152+ // Locking seems ugly, but it's difficult to properly synchronize the creation and cleanup of the underlying task without it. :/
153+ // The important part is:
154+ // 1) Only initializing once.
155+ // 2) Allowing for proper cleanup after run which requires a reference to the task.
156+ ThreadSafety . LockConditional (
157+ _taskLock ,
158+ ( ) => ( task = _task ) == null ,
159+ ( ) =>
160+ {
161+ Interlocked . Exchange ( ref _task , task = Prepare ( delay ) ) ;
162+ } ) ;
142163 return task ;
143164 }
144165
0 commit comments