首页 > 解决方案 > 线程中止延迟导致显示响应延迟

问题描述

我正在编写一个程序,它使用三个线程更新显示,两个线程都将图像写入显示表单,一个在需要条件时在两者之间切换。我将在底部包含所有三个的代码。我注意到,当没有音频提示终止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();
                    }
               }
            }
        }

标签: c#multithreadingvisual-studiowinforms

解决方案


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

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

可以为此使用 async/await,因为这将被编译为状态机。whileTask.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
            }
        }

据我所知,这应该保持相同的行为,同时在主线程上运行所有代码,并避免线程的所有麻烦。请注意,这需要在谈话/中立条件实际发生变化时通知类,我的示例使用一种方法,但它也可以是一个事件。您可能还需要一些方法来停止整个循环,这可能是简单的布尔值,或者另一个 cancelTokenSource。


推荐阅读