none
winform 多线程 UI假死问题 RRS feed

  • 问题

  • 
    
    
    正在做一个winform程序,选中listview的一行,会有一个比较耗时的操作,操作结束后会修改10几个label的值,发现选择了listview某行后,经常UI假死,选择其他的记录会停顿滞后。我想新建一个线程去处理它,每个label控件都要写委托吗?10几个实在是太多了,有没有更好的办法统一做?不容易造成UI假死
    
    

    • 已编辑 Lacuz 2013年9月29日 4:18
    2013年9月29日 4:18

答案

  • 除了使用委托还有其他的方式来更新UI的,你只需要获得UI线程的同步上下文,然后调用Post方法把更新的方法教给UI线程去处理就好了,其实委托回调也是这样机制,只是使用委托回调需要每个控件都要写委托的,其实每个控件写委托的本质就是获得该控件创建的线程,也就是UI线程,然后把这个委托包装的方法教给UI线程去处理,但是创建每个控件的线程是一样的,即是UI线程,所以你可以在窗体Load方法后面通过
    // 捕捉调用线程的同步上下文派生对象
                sc = SynchronizationContext.Current;

    来获得同步上下文,然后在选中ListView的Selected事件中调用sc.Post(包装更新控件方法的委托,传递给委托的对象)方法来更新控件的值,使用这种方式只需要定义一个委托就可以了。


    If my post is helpful,please help to vote as helpful, if my post solve your question, please help to make it as answer. My sample

    2013年9月29日 5:29
  • Lacuz,

    可以这样一些考虑:创建一个Thread,并且设置其IsBackGround=true;然后通过Form自身的Invoke方法内部更新Label的数值。这里给你一个Sample:

    【方法一】

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;
     
    namespace CSharp
    {
        public partial class Form1 : Form
        {
     
            public Form1()
            {
                InitializeComponent();
            }
     
            private void listBox1_SelectedIndexChanged(object senderEventArgs e)
            {
                //线程没有处理完,无法继续选择;每次选一条记录处理
                listBox1.Enabled = false;
                string value = listBox1.SelectedItem.ToString();
     
                Thread th = new Thread(() => 
                {
                    //模拟一个很长时间的操作
                    Thread.Sleep(Convert.ToInt32(value* 1000);
                    //改变Label
                    this.Invoke(new MethodInvoker(() => 
                    {
                        label1.Text = "用时:" + value + "秒";
                        listBox1.Enabled = true;
                    }));
                });
                th.IsBackground = true;
                th.Start();
     
            }
        }
    }

    你可以考虑在this.Invoke委托中写任意多的Label赋值语句。

    【方法二】——LearningHard的方法:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;
     
    namespace CSharp
    {
        public partial class Form1 : Form
        {
     
            public Form1()
            {
                InitializeComponent();
            }
     
            private void listBox1_SelectedIndexChanged(object senderEventArgs e)
            {
                //线程没有处理完,无法继续选择;每次选一条记录处理
                listBox1.Enabled = false;
                string value = listBox1.SelectedItem.ToString();
                //获取当前线程上下文
                SynchronizationContext context = SynchronizationContext.Current;
     
                Thread th = new Thread(() => 
                {
                    //模拟一个很长时间的操作
                    Thread.Sleep(Convert.ToInt32(value* 1000);
                    //改变Label
                    context.Post(new SendOrPostCallback(state => 
                    {
                        label1.Text = "用时:" + value + "秒";
                        listBox1.Enabled = true;
                    }),null);
                });
                th.IsBackground = true;
                th.Start();
            }
        }
    }

    Click For donating:Free Rice For the poor
    For spamming-sender issues, you can either report it at Microsoft Spamming Issue, or just find "Report Spam Here+Number" at Forum Issue;You can also find "Verify Your Account+Number" at "Forum Issue", where you can submit to be confirmed to paste links or images.
    For more things to talk about? StackOverFlow is your choice.

    • 已标记为答案 Lacuz 2013年10月1日 4:04
    2013年9月30日 9:04
    版主

全部回复

  • 试试backgroundworker

    http://feiyun0112.cnblogs.com/

    2013年9月29日 5:21
    版主
  • 除了使用委托还有其他的方式来更新UI的,你只需要获得UI线程的同步上下文,然后调用Post方法把更新的方法教给UI线程去处理就好了,其实委托回调也是这样机制,只是使用委托回调需要每个控件都要写委托的,其实每个控件写委托的本质就是获得该控件创建的线程,也就是UI线程,然后把这个委托包装的方法教给UI线程去处理,但是创建每个控件的线程是一样的,即是UI线程,所以你可以在窗体Load方法后面通过
    // 捕捉调用线程的同步上下文派生对象
                sc = SynchronizationContext.Current;

    来获得同步上下文,然后在选中ListView的Selected事件中调用sc.Post(包装更新控件方法的委托,传递给委托的对象)方法来更新控件的值,使用这种方式只需要定义一个委托就可以了。


    If my post is helpful,please help to vote as helpful, if my post solve your question, please help to make it as answer. My sample

    2013年9月29日 5:29
  • 除了使用委托还有其他的方式来更新UI的,你只需要获得UI线程的同步上下文,然后调用Post方法把更新的方法教给UI线程去处理就好了,其实委托回调也是这样机制,只是使用委托回调需要每个控件都要写委托的,其实每个控件写委托的本质就是获得该控件创建的线程,也就是UI线程,然后把这个委托包装的方法教给UI线程去处理,但是创建每个控件的线程是一样的,即是UI线程,所以你可以在窗体Load方法后面通过
    // 捕捉调用线程的同步上下文派生对象
                sc = SynchronizationContext.Current;

    来获得同步上下文,然后在选中ListView的Selected事件中调用sc.Post(包装更新控件方法的委托,传递给委托的对象)方法来更新控件的值,使用这种方式只需要定义一个委托就可以了。


    If my post is helpful,please help to vote as helpful, if my post solve your question, please help to make it as answer. My sample

    还是没看明白,能给个简单的例子吗?

    这种做法会不会造成这种现象:

    如果你使用异步处理,有可能会导致当你选中1,你的程序异步处理选择1的操作,然后你选中2,你的程序异步处理选择2的操作,并且先返回,异步处理选择1的操作后返回,导致用户选择了2,看到的是1的奇怪现象。

    
    
    • 已编辑 Lacuz 2013年9月29日 5:39
    2013年9月29日 5:36
  • 参考下面文章的第四部分:

    http://www.cnblogs.com/zhili/archive/2013/05/10/APM.html

    也可以参考下这个文章:

    http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I


    If my post is helpful,please help to vote as helpful, if my post solve your question, please help to make it as answer. My sample


    2013年9月29日 5:37
  • 如果在前一个操作没有完成的时候,选择了listview第2条数据,

    这种同步上下文的做法有办法中断掉第一条,直接执行第2条数据的post吗?

    
    2013年9月29日 6:13
  • 可以的,你可以在第一个操作中更新控件值的方法中设置一个标记值,你把更新控件的操作放在while循环中,如下:

    // 标记变量可以bool类型
    while(i<control.Length)
    {
    // 如果为false就退出循环
    if(!定义的一个标记变量)
    {
    break;
    }
    // 每次更新一个控件的值,可以定义一个更新控件的方法,参数为控件和更新的值。
    Label l=this.controls.Items[i];
    Updatecpntrol(l, 传入更新的值);
    i++;
    
    }
    // 更新控件方法
    public void updatecontrol(Label l, string text)
    {
    l.Text=text;
    }

    因为你选择第2条的数据时同样会触发Listview的Selected事件的,此时你在原来的ListView_selectedChange事件中把标记变量设置为false ,

    更多内容可以参考:http://www.cnblogs.com/zhili/archive/2013/05/11/EAP.html

    不过在新版本的.NET中是不建议使用委托回调方式来进行异步编程的,更多的是使用基于任务的异步编程模式的,具体你可以参考MSDN中异步编程专题:http://msdn.microsoft.com/zh-cn/library/jj152938.aspx


    If my post is helpful,please help to vote as helpful, if my post solve your question, please help to make it as answer. My sample


    2013年9月29日 6:36
  • Lacuz,

    可以这样一些考虑:创建一个Thread,并且设置其IsBackGround=true;然后通过Form自身的Invoke方法内部更新Label的数值。这里给你一个Sample:

    【方法一】

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;
     
    namespace CSharp
    {
        public partial class Form1 : Form
        {
     
            public Form1()
            {
                InitializeComponent();
            }
     
            private void listBox1_SelectedIndexChanged(object senderEventArgs e)
            {
                //线程没有处理完,无法继续选择;每次选一条记录处理
                listBox1.Enabled = false;
                string value = listBox1.SelectedItem.ToString();
     
                Thread th = new Thread(() => 
                {
                    //模拟一个很长时间的操作
                    Thread.Sleep(Convert.ToInt32(value* 1000);
                    //改变Label
                    this.Invoke(new MethodInvoker(() => 
                    {
                        label1.Text = "用时:" + value + "秒";
                        listBox1.Enabled = true;
                    }));
                });
                th.IsBackground = true;
                th.Start();
     
            }
        }
    }

    你可以考虑在this.Invoke委托中写任意多的Label赋值语句。

    【方法二】——LearningHard的方法:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;
     
    namespace CSharp
    {
        public partial class Form1 : Form
        {
     
            public Form1()
            {
                InitializeComponent();
            }
     
            private void listBox1_SelectedIndexChanged(object senderEventArgs e)
            {
                //线程没有处理完,无法继续选择;每次选一条记录处理
                listBox1.Enabled = false;
                string value = listBox1.SelectedItem.ToString();
                //获取当前线程上下文
                SynchronizationContext context = SynchronizationContext.Current;
     
                Thread th = new Thread(() => 
                {
                    //模拟一个很长时间的操作
                    Thread.Sleep(Convert.ToInt32(value* 1000);
                    //改变Label
                    context.Post(new SendOrPostCallback(state => 
                    {
                        label1.Text = "用时:" + value + "秒";
                        listBox1.Enabled = true;
                    }),null);
                });
                th.IsBackground = true;
                th.Start();
            }
        }
    }

    Click For donating:Free Rice For the poor
    For spamming-sender issues, you can either report it at Microsoft Spamming Issue, or just find "Report Spam Here+Number" at Forum Issue;You can also find "Verify Your Account+Number" at "Forum Issue", where you can submit to be confirmed to paste links or images.
    For more things to talk about? StackOverFlow is your choice.

    • 已标记为答案 Lacuz 2013年10月1日 4:04
    2013年9月30日 9:04
    版主
  • hello,

    你可使用 async/await处理

    http://www.dotblogs.com.tw/yc421206/archive/2013/06/14/105514.aspx

    http://www.dotblogs.com.tw/yc421206/archive/2013/08/05/113433.aspx

    http://www.dotblogs.com.tw/yc421206/archive/2013/08/07/113652.aspx

    或是使用SynchronizationContext

        public Form1()
        {
            InitializeComponent();
            m_SynchronizationContext = SynchronizationContext.Current;
    	m_SynchronizationContext.Post(a => { this.label1.Text = this._counter.ToString(); }, null);
        }
    
        private SynchronizationContext m_SynchronizationContext;

    更多的范例请参考

    https://www.google.com/search?q=SynchronizationContext&ie=UTF8&oe=UTF8&hl=zh-TW&btnG=Search&domains=http://www.dotblogs.com.tw/yc421206&sitesearch=http://www.dotblogs.com.tw/yc421206


    或是使用Invoke/BeginInvoke

    this.BeginInvoke((MethodInvoker)delegate { this.listBox1.Items.Add(DateTime.Now + " : 進入主執行緒"); });
    this.Invoke((Action)(() => { this.label1.Text = "Demo"; }));


    秘訣無它,唯勤而已 http://www.dotblogs.com.tw/yc421206/


    2013年9月30日 9:48