C#线程中关于task和.Invoke的问题

问题:当我每次点击按钮时,文本框中的数字会从0开始递增显示。但是当我启用被注释掉的代 码this.Invoke()时,程序将会变呆,按下代码编辑器中的全部中断,可以看见代码运行停留在task1.Wait()处。为什么会这样?要是我 必须保留this.Invoke((MethodInvoker)delegate (),应怎样编码?


        Task task1 = null;
        CancellationTokenSource tokenSource1 = null;
        CancellationToken token1;
        private void button1_Click(object sender, EventArgs e)
        {
            if (task1 != null && task1.Status == TaskStatus.Running)
            {
                tokenSource1.Cancel();
                task1.Wait();
            }

            task1 = null;
            tokenSource1 = new CancellationTokenSource();
            token1 = tokenSource1.Token;

            task1 = Task.Factory.StartNew(() =>
            {
                //this.Invoke((MethodInvoker)delegate ()
                //{
                    for (int i = 0; i < 20; i++)
                    {
                        if (token1.IsCancellationRequested)
                        {
                            Sound.TTS_Ms_Speak(EnuSpeakVoiceMode.Normol, "取消");
                            return;
                        }
                        this.Invoke(new Action(() => { textBox1.Text = i.ToString(); }));
                        Application.DoEvents();
                        Thread.Sleep(1000);
                    }
                //});
            }, token1);
        }
我之所以想使用this.Invoke((MethodInvoker)delegate (){},是因为涉及到跨线程访问控件。

很明显呀,你的this.Invoke((MethodInvoker)delegate ()这个方法放的位置不对呀  应该放在循环里面呀

典型的线程间race condition:
你的invoke要求把代码放在原UI线程上去做,但此时你的UI线程正在Wait()这个工作线程呢。结果就是一边UI线程在等工作线程完结,另一边工 作线程又要求nvoke()的内容完了才会继续往下跑。而这个Invoke内容是需要等UI线程自由时在UI线程上运行的,所以造成了死锁。

在winform, wpf等固定UI线程渲染的程序中,UI线程从来不用wait,也不需要用wait。UI线程需要贯彻的唯有“迅速响应”四字精要,总之别把业务代码写在各种button_click之类的响应函数里。

Q: 如果非要wait怎么办?比如接下来马上要用到那些变量。
A: C#5之后请用async/await。
Q: C#5之前呢?
A: 再启动一个定时器线程,不断检查工作线程的状态,并把剩下的逻辑都放到定时器线程里去做。总之UI线程是大爷,谁都能等,只有UI线程不能等别人。

async/await解决例:
private async void button1_Click(object sender, EventArgs e)
{
...
    await task1;

当然这个也是有隐患的:万一你的工作线程取消耗时太久,中间用户又点击了button给重入了一次这函数……就只有哭了。所以最根本的解决方法是更换你的设计逻辑:在存在你不可控的情况下,别去管它。

从逻辑上看,你取消工作线程等待期间应该是不能处理消息的。正确的方法应该是:设置一个isWaiting变量,并在响应函数头部先检查这个值,以防止用 户连续按这个按钮;在发送线程取消消息之后直接小蜜蜂论坛发帖机设置isWaiting=true并返回,并把isWaiting=false放在工作线程退出之前。如果 想要界面更友好点,还可以暂时把按钮变灰。

wait 等待任务完成,启用invoke,会同步执行for,也就是要耗时等待20s,task才会将自己的状态设置为完成状态。
在当前UI线程上执行task.wait方法会阻塞线程,也就是所谓的“变呆”,这时候线程被阻塞,不能响应其它动作,直到task完成。
使用task目的为了简化线程操作,如果在task中使用同步代码并且使用wait等待,并非await ,那就有点得不偿失了。

发布了27 篇原创文章 · 获赞 0 · 访问量 1091

猜你喜欢

转载自blog.csdn.net/netyou/article/details/104231443