线程中止延迟导致显示响应延迟

Thread Abortion Delay causes Delay in Display Response

我正在编写一个程序,该程序使用三个线程更新显示,两个线程都将图像写入显示窗体,一个在需要条件时在两个线程之间切换。我将在底部包含所有三个的代码。我注意到,当talkingThread的中止和neutralThread的创建和启动没有音频提示时,会说话的图像会在那里停留几秒钟,持续时间会有所不同。我怀疑它与线程末尾的 Sleep 调用有关,调用 Abort 实际上在清除睡眠之前不会处理线程,但我不知道这是否会影响另一个线程的写入能力显示器。有什么办法可以保证线程尽快处理掉?

中性循环 -

private void neutralLoop() //loop for switching to and maintaining the neutral images
        {  
            while (true)
            {
                if (isBlinking) //if blinking is enabled, check whether or not to blink, and hold for 1 second if so
                {
                    int blinkChance = rnd.Next(2);
                    if (blinkChance == 1)
                    {
                        myImage = this.neutralEyesClosedMap;
                        pictureBox1.Image = myImage;
                        Thread.Sleep(1000);
                    }
                }
                myImage = this.neutralEyesOpenMap; //replace neutral eyes open state, hold for 5 secs
                pictureBox1.Image = myImage;
                Thread.Sleep(5000);
            }
        }

通话循环 -

private void talkingLoop() //loop for switching to and maintaining the talking images
        {
            while (true)
            {
                if (isBlinking) //if blinking is enabled, check whether or not to blink, and hold for 1 second if so
                {
                    int blinkChance = rnd.Next(2);
                    if (blinkChance == 1)
                    {
                        myImage = this.talkingEyesClosedMap;
                        pictureBox1.Image = myImage;
                        Thread.Sleep(1000);
                    }
                }
                myImage = this.talkingEyesOpenMap; //replace neutral eyes open state, hold for 5 secs
                pictureBox1.Image = myImage;
                Thread.Sleep(5000);
            }
        }

切换循环

private void switchLoop()
        {
            neutralThread.Start();
            while (true)
            {
               if (deviceMeter.MasterPeakValue > 0 && deviceMeter.MasterPeakValue < 1) //check for audio
               {
                    if (talkingThread.ThreadState != ThreadState.Running && talkingThread.ThreadState != ThreadState.WaitSleepJoin) //if talkingThread isnt running (neutralThread is running), get rid of it and start talkingThread.
                    {
                        neutralThread.Abort();
                        talkingThread = new Thread(new ThreadStart(talkingLoop));
                        talkingThread.Start();
                    }
               } else {
                    if (neutralThread.ThreadState != ThreadState.Running && neutralThread.ThreadState != ThreadState.WaitSleepJoin) //if neutralThread isnt running (talkingThread is running), get rid of it and start neutralThread.
                    {
                        talkingThread.Abort();
                        neutralThread = new Thread(new ThreadStart(neutralLoop));
                        neutralThread.Start();
                    }
               }
            }
        }

使用 Thread.Abort() 是一个 terrible idea,可能会导致各种问题。该示例还看起来像是在后台线程上修改 UI 元素,这也是不允许的,并且可能导致各种问题。

取消某些 运行 后台任务的正确方法是合作。但是,看起来我们根本不需要使用任何后台线程。我们需要的是一个 状态机 和一个定时器。状态机跟踪我们所处的状态、要使用的图像以及当我们在状态之间切换时应该发生什么。计时器用于在一段时间后触发状态更改。

可以为此使用 async/await,因为这将被编译为状态机。而 Task.Delay 以可与 async/await 一起使用的方式包装计时器。据我所知,您可以这样重写您的示例:

        private async Task Loop() 
        {
            while (true)
            {
                cts = new CancellationTokenSource(); // replace the cancellationToken source each iteration
                try
                {
                    if (isBlinking) {
                        int blinkChance = rnd.Next(2);
                        if (blinkChance == 1)
                        {
                            pictureBox1.Image = isneutral ? neutralEyesClosedMap : talkingEyesClosedMap;
                            await Task.Delay(1000, cts.Token);
                        }
                    } 
                    pictureBox1.Image = isneutral ? neutralEyesOpenMap : talkingEyesOpenMap;
                    await Task.Delay(5000, cts.Token);
                }
                // Task.Delay may throw when isneutral changes state, this is expected, just continue
                catch(OperationCanceledException){} 
            }
        }
        public async Task SetMasterPeakValue(double value)
        {
            var oldValue = isneutral;
            isneutral = value > 0 && value < 1;
            if (oldValue != isneutral)
            {
                cts.Cancel(); // cancel the current loop
            }
        }

据我所知,这应该保持相同的行为,而 运行 主线程上的所有代码,并避免线程的所有麻烦。请注意,这需要在 talking/neutral 条件实际更改时通知 class,我的示例使用了一个方法,但它也可以是一个事件。您可能还需要一些方法来停止整个循环,这可以是简单的布尔值或另一个 cancellationTokenSource。