diff --git a/src/SquidStd.Core/Interfaces/Threading/IEventLoopService.cs b/src/SquidStd.Core/Interfaces/Threading/IEventLoopService.cs
index b6d5af8..19e01b6 100644
--- a/src/SquidStd.Core/Interfaces/Threading/IEventLoopService.cs
+++ b/src/SquidStd.Core/Interfaces/Threading/IEventLoopService.cs
@@ -15,4 +15,7 @@ public interface IEventLoopService
/// Worst observed tick elapsed time in milliseconds.
double MaxTickMs { get; }
+
+ /// True when the calling thread is the dedicated event-loop thread.
+ bool IsOnLoopThread { get; }
}
diff --git a/src/SquidStd.Services.Core/Services/EventLoop/EventLoopService.cs b/src/SquidStd.Services.Core/Services/EventLoop/EventLoopService.cs
index d2e9a03..9edf5c5 100644
--- a/src/SquidStd.Services.Core/Services/EventLoop/EventLoopService.cs
+++ b/src/SquidStd.Services.Core/Services/EventLoop/EventLoopService.cs
@@ -28,6 +28,7 @@ public sealed class EventLoopService : IEventLoopService, ISquidStdService, IMet
private double _maxTickMs;
private Thread? _thread;
private long _tickCount;
+ private volatile int _loopThreadId = -1;
///
public long TickCount => Interlocked.Read(ref _tickCount);
@@ -56,6 +57,9 @@ public double MaxTickMs
}
}
+ ///
+ public bool IsOnLoopThread => _loopThreadId == Environment.CurrentManagedThreadId;
+
///
public string ProviderName => "eventloop";
@@ -85,6 +89,9 @@ public ValueTask StopAsync(CancellationToken cancellationToken = default)
_cts.Cancel();
_thread?.Join(TimeSpan.FromSeconds(5));
+ // Reset so a recycled managed thread id can't later report as the loop thread.
+ _loopThreadId = -1;
+
return ValueTask.CompletedTask;
}
@@ -136,6 +143,8 @@ internal int Tick()
private void RunLoop()
{
+ _loopThreadId = Environment.CurrentManagedThreadId;
+
while (!_cts.IsCancellationRequested)
{
var work = Tick();
diff --git a/tests/SquidStd.Tests/EventLoop/EventLoopServiceTests.cs b/tests/SquidStd.Tests/EventLoop/EventLoopServiceTests.cs
index 784e222..dfda3a2 100644
--- a/tests/SquidStd.Tests/EventLoop/EventLoopServiceTests.cs
+++ b/tests/SquidStd.Tests/EventLoop/EventLoopServiceTests.cs
@@ -80,4 +80,30 @@ public async Task StartStop_ExecutesPostedWork()
Assert.True(fired);
}
+
+ [Fact]
+ public async Task IsOnLoopThread_TrueInsidePostedWork_FalseOutside()
+ {
+ var dispatcher = new MainThreadDispatcherService();
+ var timer = new FakeTimerService();
+ using var loop = new EventLoopService(dispatcher, timer, new EventLoopConfig());
+
+ Assert.False(loop.IsOnLoopThread);
+
+ bool onLoopInside = false;
+ var signal = new ManualResetEventSlim(false);
+ dispatcher.Post(() =>
+ {
+ onLoopInside = loop.IsOnLoopThread;
+ signal.Set();
+ }
+ );
+
+ await loop.StartAsync();
+ signal.Wait(TimeSpan.FromSeconds(2));
+ await loop.StopAsync();
+
+ Assert.True(onLoopInside);
+ Assert.False(loop.IsOnLoopThread);
+ }
}