forked from smartstore/Smartstore
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6990b24
commit d12f27c
Showing
1 changed file
with
111 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
using System.Diagnostics; | ||
|
||
namespace Smartstore.Threading | ||
{ | ||
public class PortableTimer : IDisposable | ||
{ | ||
private readonly object _stateLock = new(); | ||
private readonly Func<CancellationToken, Task> _onTick; | ||
private readonly CancellationTokenSource _cancel = new(); | ||
private readonly Timer _timer; | ||
|
||
private bool _running; | ||
private bool _disposed; | ||
|
||
public PortableTimer(Func<CancellationToken, Task> onTick) | ||
{ | ||
_onTick = Guard.NotNull(onTick, nameof(onTick)); | ||
|
||
using (ExecutionContext.SuppressFlow()) | ||
{ | ||
_timer = new(_ => OnTick(), null, Timeout.Infinite, Timeout.Infinite); | ||
} | ||
} | ||
|
||
public void Start(TimeSpan interval) | ||
{ | ||
if (interval < TimeSpan.Zero) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(interval)); | ||
} | ||
|
||
lock (_stateLock) | ||
{ | ||
if (_disposed) | ||
{ | ||
throw new ObjectDisposedException(nameof(PortableTimer)); | ||
} | ||
|
||
_timer.Change(interval, interval); | ||
} | ||
} | ||
|
||
async void OnTick() | ||
{ | ||
try | ||
{ | ||
lock (_stateLock) | ||
{ | ||
if (_disposed) | ||
{ | ||
return; | ||
} | ||
|
||
// There's a little bit of raciness here, but it's needed to support the | ||
// current API, which allows the tick handler to reenter and set the next interval. | ||
|
||
if (_running) | ||
{ | ||
Monitor.Wait(_stateLock); | ||
|
||
if (_disposed) | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
_running = true; | ||
} | ||
|
||
if (!_cancel.Token.IsCancellationRequested) | ||
{ | ||
ContextState.StartAsyncFlow(); | ||
await _onTick(_cancel.Token); | ||
} | ||
} | ||
catch (OperationCanceledException tcx) | ||
{ | ||
Debug.WriteLine("The timer was canceled during invocation: {0}", tcx); | ||
} | ||
finally | ||
{ | ||
lock (_stateLock) | ||
{ | ||
_running = false; | ||
Monitor.PulseAll(_stateLock); | ||
} | ||
} | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_cancel.Cancel(); | ||
|
||
lock (_stateLock) | ||
{ | ||
if (_disposed) | ||
{ | ||
return; | ||
} | ||
|
||
while (_running) | ||
{ | ||
Monitor.Wait(_stateLock); | ||
} | ||
|
||
_timer.Dispose(); | ||
_disposed = true; | ||
} | ||
} | ||
} | ||
} |