using System; using System.Linq; using System.Collections.Generic; using System.Collections.Concurrent; using System.Reflection; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using var gate = new SemaphoreSlim(1, 1); using var acquired = new ManualResetEventSlim(); bool released = false; OwnedThreads.Run( () => { gate.Wait(); acquired.Set(); }, () => { if (!acquired.Wait(TimeSpan.FromSeconds(5))) throw new TimeoutException(); gate.Release(); released = true; }); Console.WriteLine($"cross-thread-release:{released}"); Console.WriteLine($"permits:{gate.CurrentCount}"); public static class OwnedThreads { public static void Run(params Action[] actions) { var errors = new ConcurrentQueue(); var threads = actions.Select(action => new Thread(() => { try { action(); } catch (Exception error) { errors.Enqueue(error); } }) { IsBackground = true }).ToArray(); foreach (var thread in threads) thread.Start(); foreach (var thread in threads) if (!thread.Join(TimeSpan.FromSeconds(8))) throw new TimeoutException("Owned worker did not finish"); if (!errors.IsEmpty) throw new AggregateException(errors); } }