示例#1
0
 void CreateLongRunningTask(XParameterizedThreadStart threadStartFunc)
 {
     this.task = Task.Factory.StartNew(
         () =>
     {
         // We start the task running, then unleash it by signaling the readyToStart event.
         // This is needed to avoid thread reuse for tasks (see below)
         this.readyToStart.WaitOne();
         // This is the first time we're using this thread, therefore the TLS slot must be empty
         if (currentThread != null)
         {
             Debug.WriteLine("warning: currentThread already created; OS thread reused");
             Debug.Assert(false);
         }
         currentThread = this;
         threadStartFunc(this.startupParameter);
         this.completed.Set();
     },
         CancellationToken.None,
         // .NET always creates a brand new thread for LongRunning tasks
         // This is not documented but unlikely to ever change:
         // https://github.com/dotnet/corefx/issues/2576#issuecomment-126693306
         TaskCreationOptions.LongRunning,
         TaskScheduler.Default);
 }
示例#2
0
        /// <summary>
        /// Cancels the task scheduled via <see cref="Watch"/>.
        /// </summary>
        public static void Unwatch(Thread thread, Action task)
        {
            Contract.Requires(thread != null);
            Contract.Requires(task != null);

            Schedule(thread, task, false);
        }
示例#3
0
            public override void ChannelRead(IChannelHandlerContext context, object message)
            {
                var t = _thread;

                if (t is null)
                {
                    _thread = Thread.CurrentThread;
                }
                else
                {
                    Assert.Same(t, Thread.CurrentThread);
                }

                IByteBuffer m     = (IByteBuffer)message;
                int         count = m.ReadableBytes / 4;

                for (int j = 0; j < count; j++)
                {
                    int actual   = m.ReadInt();
                    int expected = _inCnt++;
                    Assert.Equal(expected, actual);
                    context.FireChannelRead(actual);
                }
                m.Release();
            }
示例#4
0
        protected SingleThreadEventExecutorOld(IEventExecutorGroup parent, string threadName, TimeSpan breakoutInterval, IQueue <IRunnable> taskQueue)
            : base(parent)
        {
            _firstTask     = true;
            _emptyEvent    = new ManualResetEventSlim(false, 1);
            _shutdownHooks = new HashSet <Action>();

            _loopAction     = Loop;
            _loopCoreAciton = LoopCore;

            _terminationCompletionSource = NewPromise();
            _taskQueue = taskQueue;
            _preciseBreakoutInterval = PreciseTimeSpan.FromTimeSpan(breakoutInterval);
            _scheduler = new ExecutorTaskScheduler(this);
            _thread    = new Thread(_loopAction);
            if (string.IsNullOrEmpty(threadName))
            {
                _thread.Name = DefaultWorkerThreadName;
            }
            else
            {
                _thread.Name = threadName;
            }
            _thread.Start();
        }
示例#5
0
        void DestroyDown(Thread currentThread, AbstractChannelHandlerContext ctx, bool inEventLoop)
        {
            // We have reached at tail; now traverse backwards.
            AbstractChannelHandlerContext headContext = this.head;

            while (true)
            {
                if (ctx == headContext)
                {
                    break;
                }

                IEventExecutor executor = ctx.Executor;
                if (inEventLoop || executor.IsInEventLoop(currentThread))
                {
                    lock (this)
                    {
                        Remove0(ctx);
                        this.CallHandlerRemoved0(ctx);
                    }
                }
                else
                {
                    executor.Execute((self, c) => ((DefaultChannelPipeline)self).DestroyDown(Thread.CurrentThread, (AbstractChannelHandlerContext)c, true), this, ctx);
                    break;
                }

                ctx         = ctx.Prev;
                inEventLoop = false;
            }
        }
示例#6
0
        /// <summary>
        /// Schedules the specified <see cref="Action"/> to run when the specified <see cref="Thread"/> dies.
        /// </summary>
        public static void Watch(Thread thread, Action task)
        {
            Contract.Requires(thread != null);
            Contract.Requires(task != null);
            Contract.Requires(thread.IsAlive);

            Schedule(thread, task, true);
        }
        private bool ConfirmShutdownSlow()
        {
            if (!InEventLoop)
            {
                ThrowHelper.ThrowInvalidOperationException_Must_be_invoked_from_an_event_loop();
            }

            CancelScheduledTasks();

            if (0ul >= (ulong)_gracefulShutdownStartTime)
            {
                _gracefulShutdownStartTime = GetTimeFromStart();
            }

            if (RunAllTasks() || RunShutdownHooks())
            {
                if (IsShutdown)
                {
                    // Executor shut down - no new tasks anymore.
                    return(true);
                }

                // There were tasks in the queue. Wait a little bit more until no tasks are queued for the quiet period or
                // terminate if the quiet period is 0.
                // See https://github.com/netty/netty/issues/4241
                if (0ul >= (ulong)Volatile.Read(ref v_gracefulShutdownQuietPeriod))
                {
                    return(true);
                }
                _taskQueue.TryEnqueue(WakeupTask);
                return(false);
            }

            long nanoTime = GetTimeFromStart();

            if (IsShutdown || (nanoTime - _gracefulShutdownStartTime > Volatile.Read(ref v_gracefulShutdownTimeout)))
            {
                return(true);
            }

            if (nanoTime - _lastExecutionTime <= Volatile.Read(ref v_gracefulShutdownQuietPeriod))
            {
                // Check if any tasks were added to the queue every 100ms.
                // TODO: Change the behavior of takeTask() so that it returns on timeout.
                _taskQueue.TryEnqueue(WakeupTask);

                Thread.Sleep(100);

                return(false);
            }

            // No tasks were added for last quiet period - hopefully safe to shut down.
            // (Hopefully because we really cannot make a guarantee that there will be no execute() calls by a user.)
            return(true);
        }
示例#8
0
        static void Schedule(Thread thread, Action task, bool isWatch)
        {
            _ = PendingEntries.TryEnqueue(new Entry(thread, task, isWatch));

            if (SharedConstants.False >= (uint)Interlocked.CompareExchange(ref started, SharedConstants.True, SharedConstants.False))
            {
                var watcherThread = new Thread(s => ((IRunnable)s).Run());
                watcherThread.Start(watcher);
                _ = Interlocked.Exchange(ref ThreadDeathWatcher.watcherThread, watcherThread);
            }
        }
示例#9
0
        static void Schedule(Thread thread, Action task, bool isWatch)
        {
            PendingEntries.TryEnqueue(new Entry(thread, task, isWatch));

            if (Interlocked.CompareExchange(ref started, 1, 0) == 0)
            {
                var watcherThread = new Thread(s => ((IRunnable)s).Run());
                watcherThread.Start(watcher);
                ThreadDeathWatcher.watcherThread = watcherThread;
            }
        }
示例#10
0
        protected bool ConfirmShutdownSlow()
        {
            Debug.Assert(InEventLoop, "must be invoked from an event loop");

            CancelScheduledTasks();

            if (_gracefulShutdownStartTime == PreciseTimeSpan.Zero)
            {
                _gracefulShutdownStartTime = PreciseTimeSpan.FromStart;
            }

            if (RunAllTasks() || RunShutdownHooks())
            {
                if (IsShutdown)
                {
                    // Executor shut down - no new tasks anymore.
                    return(true);
                }

                // There were tasks in the queue. Wait a little bit more until no tasks are queued for the quiet period or
                // terminate if the quiet period is 0.
                // See https://github.com/netty/netty/issues/4241
                if (_gracefulShutdownQuietPeriod == PreciseTimeSpan.Zero)
                {
                    return(true);
                }
                WakeUp(true);
                return(false);
            }

            PreciseTimeSpan nanoTime = PreciseTimeSpan.FromStart;

            if (IsShutdown || (nanoTime - _gracefulShutdownStartTime > _gracefulShutdownTimeout))
            {
                return(true);
            }

            if (nanoTime - _lastExecutionTime <= _gracefulShutdownQuietPeriod)
            {
                // Check if any tasks were added to the queue every 100ms.
                // TODO: Change the behavior of takeTask() so that it returns on timeout.
                // todo: ???
                WakeUp(true);
                Thread.Sleep(100);

                return(false);
            }

            // No tasks were added for last quiet period - hopefully safe to shut down.
            // (Hopefully because we really cannot make a guarantee that there will be no execute() calls by a user.)
            return(true);
        }
示例#11
0
        /// <summary>
        /// Cancels the task scheduled via <see cref="Watch"/>.
        /// </summary>
        public static void Unwatch(Thread thread, Action task)
        {
            if (thread is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.thread);
            }
            if (task is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task);
            }

            Schedule(thread, task, false);
        }
示例#12
0
        protected bool ConfirmShutdown()
        {
            if (!this.IsShuttingDown)
            {
                return(false);
            }

            Contract.Assert(this.InEventLoop, "must be invoked from an event loop");

            this.CancelScheduledTasks();

            if (this.gracefulShutdownStartTime == PreciseTimeSpan.Zero)
            {
                this.gracefulShutdownStartTime = PreciseTimeSpan.FromStart;
            }

            if (this.RunAllTasks() || this.RunShutdownHooks())
            {
                if (this.IsShutdown)
                {
                    // Executor shut down - no new tasks anymore.
                    return(true);
                }

                // There were tasks in the queue. Wait a little bit more until no tasks are queued for the quiet period.
                this.WakeUp(true);
                return(false);
            }

            PreciseTimeSpan nanoTime = PreciseTimeSpan.FromStart;

            if (this.IsShutdown || (nanoTime - this.gracefulShutdownStartTime > this.gracefulShutdownTimeout))
            {
                return(true);
            }

            if (nanoTime - this.lastExecutionTime <= this.gracefulShutdownQuietPeriod)
            {
                // Check if any tasks were added to the queue every 100ms.
                // TODO: Change the behavior of takeTask() so that it returns on timeout.
                // todo: ???
                this.WakeUp(true);
                Thread.Sleep(100);

                return(false);
            }

            // No tasks were added for last quiet period - hopefully safe to shut down.
            // (Hopefully because we really cannot make a guarantee that there will be no execute() calls by a user.)
            return(true);
        }
示例#13
0
        /// <summary>
        /// Waits until the thread of this watcher has no threads to watch and terminates itself.
        /// Because a new watcher thread will be started again on <see cref="Watch"/>,
        /// this operation is only useful when you want to ensure that the watcher thread is terminated
        /// <strong>after</strong> your application is shut down and there's no chance of calling <see cref="Watch"/>
        /// afterwards.
        /// </summary>
        /// <param name="timeout"></param>
        /// <returns><c>true</c> if and only if the watcher thread has been terminated.</returns>
        public static bool AwaitInactivity(TimeSpan timeout)
        {
            Thread watcherThread = Volatile.Read(ref ThreadDeathWatcher.watcherThread);

            if (watcherThread is object)
            {
                _ = watcherThread.Join(timeout);
                return(!watcherThread.IsAlive);
            }
            else
            {
                return(true);
            }
        }
示例#14
0
        /// <summary>
        /// Waits until the thread of this watcher has no threads to watch and terminates itself.
        /// Because a new watcher thread will be started again on <see cref="Watch"/>,
        /// this operation is only useful when you want to ensure that the watcher thread is terminated
        /// <strong>after</strong> your application is shut down and there's no chance of calling <see cref="Watch"/>
        /// afterwards.
        /// </summary>
        /// <param name="timeout"></param>
        /// <returns><c>true</c> if and only if the watcher thread has been terminated.</returns>
        public static bool AwaitInactivity(TimeSpan timeout)
        {
            Thread watcherThread = ThreadDeathWatcher.watcherThread;

            if (watcherThread != null)
            {
                watcherThread.Join(timeout);
                return(!watcherThread.IsAlive);
            }
            else
            {
                return(true);
            }
        }
示例#15
0
        /// <summary>
        /// Schedules the specified <see cref="Action"/> to run when the specified <see cref="Thread"/> dies.
        /// </summary>
        public static void Watch(Thread thread, Action task)
        {
            if (thread is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.thread);
            }
            if (task is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.task);
            }
            //if (!thread.IsAlive) { ThrowHelper.ThrowArgumentException(); }

            Schedule(thread, task, true);
        }
        /// <summary>Creates a new instance of <see cref="SingleThreadEventExecutor"/>.</summary>
        /// <param name="parent">the <see cref="IEventExecutorGroup"/> which is the parent of this instance and belongs to it.</param>
        /// <param name="threadFactory">the <see cref="IThreadFactory"/> which will be used for the used <see cref="Thread"/>.</param>
        /// <param name="addTaskWakesUp"><c>true</c> if and only if invocation of <see cref="AddTask(IRunnable)"/> will wake up the executor thread.</param>
        /// <param name="maxPendingTasks">the maximum number of pending tasks before new tasks will be rejected.</param>
        /// <param name="rejectedHandler">the <see cref="IRejectedExecutionHandler"/> to use.</param>
        protected SingleThreadEventExecutor(IEventExecutorGroup parent, IThreadFactory threadFactory, bool addTaskWakesUp,
                                            int maxPendingTasks, IRejectedExecutionHandler rejectedHandler)
            : this(parent, addTaskWakesUp, rejectedHandler)
        {
            if (threadFactory is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.threadFactory);
            }

            _maxPendingTasks   = Math.Max(16, maxPendingTasks);
            _taskQueue         = NewTaskQueue(_maxPendingTasks);
            _blockingTaskQueue = _taskQueue as IBlockingQueue <IRunnable>;

            _thread = NewThread(threadFactory);
        }
示例#17
0
            public override void ChannelRead(IChannelHandlerContext context, object message)
            {
                var t = _thread;

                if (t is null)
                {
                    _thread = Thread.CurrentThread;
                }
                else
                {
                    Assert.Same(t, Thread.CurrentThread);
                }
                int actual   = (int)message;
                int expected = _inCnt++;

                Assert.Equal(expected, actual);
            }
示例#18
0
            public void Run()
            {
                for (;;)
                {
                    this.FetchWatchees();
                    this.NotifyWatchees();

                    // Try once again just in case notifyWatchees() triggered watch() or unwatch().
                    this.FetchWatchees();
                    this.NotifyWatchees();

                    Thread.Sleep(1000);

                    if (this.watchees.Count == 0 && PendingEntries.IsEmpty)
                    {
                        // Mark the current worker thread as stopped.
                        // The following CAS must always success and must be uncontended,
                        // because only one watcher thread should be running at the same time.
                        bool stopped = Interlocked.CompareExchange(ref started, 0, 1) == 1;
                        Contract.Assert(stopped);

                        // Check if there are pending entries added by watch() while we do CAS above.
                        if (PendingEntries.IsEmpty)
                        {
                            // A) watch() was not invoked and thus there's nothing to handle
                            //    -> safe to terminate because there's nothing left to do
                            // B) a new watcher thread started and handled them all
                            //    -> safe to terminate the new watcher thread will take care the rest
                            break;
                        }

                        // There are pending entries again, added by watch()
                        if (Interlocked.CompareExchange(ref started, 1, 0) != 0)
                        {
                            // watch() started a new watcher thread and set 'started' to true.
                            // -> terminate this thread so that the new watcher reads from pendingEntries exclusively.
                            break;
                        }

                        // watch() added an entry, but this worker was faster to set 'started' to true.
                        // i.e. a new watcher thread was not started
                        // -> keep this thread alive to handle the newly added entries.
                    }
                }
            }
示例#19
0
 protected SingleThreadEventExecutor(IEventExecutorGroup parent, string threadName, TimeSpan breakoutInterval, IQueue <IRunnable> taskQueue)
     : base(parent)
 {
     this.terminationCompletionSource = new TaskCompletionSource();
     this.taskQueue = taskQueue;
     this.preciseBreakoutInterval = PreciseTimeSpan.FromTimeSpan(breakoutInterval);
     this.scheduler = new ExecutorTaskScheduler(this);
     this.thread    = new Thread(this.Loop);
     if (string.IsNullOrEmpty(threadName))
     {
         this.thread.Name = DefaultWorkerThreadName;
     }
     else
     {
         this.thread.Name = threadName;
     }
     this.thread.Start();
 }
        /// <summary>Creates a new instance of <see cref="SingleThreadEventExecutor"/>.</summary>
        /// <param name="parent">the <see cref="IEventExecutorGroup"/> which is the parent of this instance and belongs to it.</param>
        /// <param name="threadFactory">the <see cref="IThreadFactory"/> which will be used for the used <see cref="Thread"/>.</param>
        /// <param name="addTaskWakesUp"><c>true</c> if and only if invocation of <see cref="AddTask(IRunnable)"/> will wake up the executor thread.</param>
        /// <param name="taskQueue">The pending task queue.</param>
        /// <param name="rejectedHandler">the <see cref="IRejectedExecutionHandler"/> to use.</param>
        protected SingleThreadEventExecutor(IEventExecutorGroup parent, IThreadFactory threadFactory, bool addTaskWakesUp,
                                            IQueue <IRunnable> taskQueue, IRejectedExecutionHandler rejectedHandler)
            : this(parent, addTaskWakesUp, rejectedHandler)
        {
            if (threadFactory is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.threadFactory);
            }
            if (taskQueue is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.taskQueue);
            }

            _maxPendingTasks   = DefaultMaxPendingExecutorTasks;
            _taskQueue         = taskQueue;
            _blockingTaskQueue = taskQueue as IBlockingQueue <IRunnable>;

            _thread = NewThread(threadFactory);
        }
示例#21
0
        protected SingleThreadEventExecutor(IEventExecutorGroup parent, string threadName, TimeSpan breakoutInterval, IQueue <IRunnable> taskQueue)
            : base(parent)
        {
            _loopAction     = Loop;
            _loopCoreAciton = LoopCore;

            _terminationCompletionSource = NewPromise();
            _taskQueue = taskQueue;
            _preciseBreakoutInterval = PreciseTimeSpan.FromTimeSpan(breakoutInterval);
            _scheduler = new ExecutorTaskScheduler(this);
            _thread    = new Thread(_loopAction);
            if (string.IsNullOrEmpty(threadName))
            {
                _thread.Name = DefaultWorkerThreadName;
            }
            else
            {
                _thread.Name = threadName;
            }
            _thread.Start();
        }
示例#22
0
            public override void ChannelRead(IChannelHandlerContext context, object message)
            {
                var t = _thread;

                if (t is null)
                {
                    _thread = Thread.CurrentThread;
                }
                else
                {
                    Assert.Same(t, Thread.CurrentThread);
                }

                IByteBuffer output   = context.Allocator.Buffer(4);
                int         m        = ((int)message);
                int         expected = _inCnt++;

                Assert.Equal(expected, m);
                output.WriteInt(m);

                context.FireChannelRead(output);
            }
示例#23
0
        void DestroyUp(AbstractChannelHandlerContext ctx, bool inEventLoop)
        {
            Thread currentThread = Thread.CurrentThread;
            AbstractChannelHandlerContext tailContext = this.tail;

            while (true)
            {
                if (ctx == tailContext)
                {
                    this.DestroyDown(currentThread, tailContext.Prev, inEventLoop);
                    break;
                }

                IEventExecutor executor = ctx.Executor;
                if (!inEventLoop && !executor.IsInEventLoop(currentThread))
                {
                    executor.Execute((self, c) => ((DefaultChannelPipeline)self).DestroyUp((AbstractChannelHandlerContext)c, true), this, ctx);
                    break;
                }

                ctx         = ctx.Next;
                inEventLoop = false;
            }
        }
示例#24
0
 public Entry(Thread thread, Action task, bool isWatch)
 {
     this.Thread  = thread;
     this.Task    = task;
     this.IsWatch = isWatch;
 }
示例#25
0
 /// <inheritdoc cref="IEventExecutor"/>
 public abstract bool IsInEventLoop(Thread thread);
示例#26
0
 public override bool IsInEventLoop(Thread thread) => true;
示例#27
0
 /// <inheritdoc cref="IEventExecutor"/>
 public override bool IsInEventLoop(Thread t) => this.thread == t;
示例#28
0
        public async Task TestStagedExecution()
        {
            IEventLoopGroup     l  = new DefaultEventLoopGroup(4, new DefaultThreadFactory("l"));
            IEventExecutorGroup e1 = new DefaultEventExecutorGroup(4, new DefaultThreadFactory("e1"));
            IEventExecutorGroup e2 = new DefaultEventExecutorGroup(4, new DefaultThreadFactory("e2"));
            ThreadNameAuditor   h1 = new ThreadNameAuditor();
            ThreadNameAuditor   h2 = new ThreadNameAuditor();
            ThreadNameAuditor   h3 = new ThreadNameAuditor(true);

            IChannel ch = new LocalChannel();

            // With no EventExecutor specified, h1 will be always invoked by EventLoop 'l'.
            ch.Pipeline.AddLast(h1);
            // h2 will be always invoked by EventExecutor 'e1'.
            ch.Pipeline.AddLast(e1, h2);
            // h3 will be always invoked by EventExecutor 'e2'.
            ch.Pipeline.AddLast(e2, h3);

            await l.RegisterAsync(ch);

            await ch.ConnectAsync(_localAddr);

            // Fire inbound events from all possible starting points.
            ch.Pipeline.FireChannelRead("1");
            ch.Pipeline.Context(h1).FireChannelRead("2");
            ch.Pipeline.Context(h2).FireChannelRead("3");
            ch.Pipeline.Context(h3).FireChannelRead("4");
            // Fire outbound events from all possible starting points.
            ch.Pipeline.WriteAsync("5").Ignore();
            ch.Pipeline.Context(h3).WriteAsync("6").Ignore();
            ch.Pipeline.Context(h2).WriteAsync("7").Ignore();
            await ch.Pipeline.Context(h1).WriteAndFlushAsync("8");

            await ch.CloseAsync();

            // Wait until all events are handled completely.
            while (h1._outboundThreadNames.Count < 3 || h3._inboundThreadNames.Count < 3 ||
                   h1._removalThreadNames.Count < 1)
            {
                if (h1._exception.Value != null)
                {
                    throw h1._exception.Value;
                }
                if (h2._exception.Value != null)
                {
                    throw h2._exception.Value;
                }
                if (h3._exception.Value != null)
                {
                    throw h3._exception.Value;
                }

                Thread.Sleep(10);
            }

            string currentName = Thread.CurrentThread.Name;

            try
            {
                // Events should never be handled from the current thread.
                Assert.DoesNotContain(currentName, h1._inboundThreadNames);
                Assert.DoesNotContain(currentName, h2._inboundThreadNames);
                Assert.DoesNotContain(currentName, h3._inboundThreadNames);
                Assert.DoesNotContain(currentName, h1._outboundThreadNames);
                Assert.DoesNotContain(currentName, h2._outboundThreadNames);
                Assert.DoesNotContain(currentName, h3._outboundThreadNames);
                Assert.DoesNotContain(currentName, h1._removalThreadNames);
                Assert.DoesNotContain(currentName, h2._removalThreadNames);
                Assert.DoesNotContain(currentName, h3._removalThreadNames);

                // Assert that events were handled by the correct executor.
                foreach (string name in h1._inboundThreadNames)
                {
                    Assert.StartsWith("l-", name);
                }
                foreach (string name in h2._inboundThreadNames)
                {
                    Assert.StartsWith("e1-", name);
                }
                foreach (string name in h3._inboundThreadNames)
                {
                    Assert.StartsWith("e2-", name);
                }
                foreach (string name in h1._outboundThreadNames)
                {
                    Assert.StartsWith("l-", name);
                }
                foreach (string name in h2._outboundThreadNames)
                {
                    Assert.StartsWith("e1-", name);
                }
                foreach (string name in h3._outboundThreadNames)
                {
                    Assert.StartsWith("e2-", name);
                }
                foreach (string name in h1._removalThreadNames)
                {
                    Assert.StartsWith("l-", name);
                }
                foreach (string name in h2._removalThreadNames)
                {
                    Assert.StartsWith("e1-", name);
                }
                foreach (string name in h3._removalThreadNames)
                {
                    Assert.StartsWith("e2-", name);
                }

                // Assert that the events for the same handler were handled by the same thread.
                HashSet <string> names = new HashSet <string>();
                names.UnionWith(h1._inboundThreadNames);
                names.UnionWith(h1._outboundThreadNames);
                names.UnionWith(h1._removalThreadNames);
                Assert.Single(names);

                names.Clear();
                names.UnionWith(h2._inboundThreadNames);
                names.UnionWith(h2._outboundThreadNames);
                names.UnionWith(h2._removalThreadNames);
                Assert.Single(names);

                names.Clear();
                names.UnionWith(h3._inboundThreadNames);
                names.UnionWith(h3._outboundThreadNames);
                names.UnionWith(h3._removalThreadNames);
                Assert.Single(names);

                // Count the number of events
                Assert.Single(h1._inboundThreadNames);
                Assert.Equal(2, h2._inboundThreadNames.Count);
                Assert.Equal(3, h3._inboundThreadNames.Count);
                Assert.Equal(3, h1._outboundThreadNames.Count);
                Assert.Equal(2, h2._outboundThreadNames.Count);
                Assert.Single(h3._outboundThreadNames);
                Assert.Single(h1._removalThreadNames);
                Assert.Single(h2._removalThreadNames);
                Assert.Single(h3._removalThreadNames);
            }
            catch (Exception)
            {
                //System.out.println("H1I: " + h1.inboundThreadNames);
                //System.out.println("H2I: " + h2.inboundThreadNames);
                //System.out.println("H3I: " + h3.inboundThreadNames);
                //System.out.println("H1O: " + h1.outboundThreadNames);
                //System.out.println("H2O: " + h2.outboundThreadNames);
                //System.out.println("H3O: " + h3.outboundThreadNames);
                //System.out.println("H1R: " + h1.removalThreadNames);
                //System.out.println("H2R: " + h2.removalThreadNames);
                //System.out.println("H3R: " + h3.removalThreadNames);
                throw;
            }
            finally
            {
                Task.WaitAll(
                    l.ShutdownGracefullyAsync(),
                    e1.ShutdownGracefullyAsync(),
                    e2.ShutdownGracefullyAsync());
            }
        }
 protected virtual void TaskDelay(int millisecondsTimeout)
 {
     Thread.Sleep(millisecondsTimeout);
 }