• C# winform多线程的小例子


    在文本框中输入一个数字,点击开始累加按钮,程序计算从1开始累计到该数字的结果。因为该累加过程比较耗时,如果直接在UI线程中进行,那么当前窗口将出现假死。为了有更好的用户体验,程序启动一个新的线程来单独执行该计算,然后每隔200毫秒读取一次累加结果,并把结果显示到文本框下方的label控件中。同时,程序支持取消操作,点击取消累计按钮,程序将取消累加操作,并把当前累加值显示到label中。为了方便后面的描述,我把UI线程称作主线程,把执行累加计算的线程称作工作者线程。该过程有两个关键点:

    1:如何在工作者线程中访问主线程创建的控件;

    2:如何取消比较耗时的计算;

    为了便于在工作者线程中调用累加过程,我把它写成一个单独方法,如下:

             /// <summary>
            /// 从1累加到指定的值,为了让该方法支持取消操作所以需要CancellationToken参数
            /// </summary>
            /// <param name="countTo">累加到的指定值</param>
            /// <param name="ct">取消凭证</param>
            private void CountTo(int countTo, CancellationToken ct) 
            {
                int sum = 0;
                for (; countTo > 0; countTo--) 
               {
                    if (ct.IsCancellationRequested) {
                        break;
                    }
                    sum += countTo;
                    //Invoke方法用于获得创建lbl_Status的线程所在的上下文
                    this.Invoke(new Action(()=>lbl_Status.Text = sum.ToString()));                
                    Thread.Sleep(200);
                }
            }                

    该方法就是用于累加数字,它有两个需要注意的地方

    1:方法需要传递一个CancellationToken参数,用于支持取消操作(《clr via c# 3版》中把这种方式称作协作式取消,也就是说某一个操作必须支持取消,然后才能取消该操作);

    2:为了允许工作者线程访问主线程创建的lbl_Status控件,我在该线程中使用this.Invoke方法。该方法用于获得主线程所创建控件的访问权。它需要一个委托作为参数,在该委托中我们可以定义对lbl_Status的操作。例如在上例中我就是把当前的累加结果赋给lbl_Status的Text属性。

    然后我们看一下如何在一个共走着线程中执行计算耗时的操作,也就是“开始累加”按钮的操作:

            private void btn_Count_Click(object sender, EventArgs e)
            {
                _cts = new CancellationTokenSource();
                ThreadPool.QueueUserWorkItem(state=>CountTo(int.Parse(txt_CountTo.Text)));
            }    

    我使用线程池线程来执行该操作,之所以使用线程池线程而不是自己的Threading对象,是因为线程池是由.NET FrameWork进行维护,默认已经为我们创建好了一些线程,从而省去创建新线程造成的一些列资源消耗,同时,完成计算任务后该线程池线程会自动回到池中等待下一个任务。我把_cts作为一个成员变量,声明如下:

    private CancellationTokenSource _cts;

    它需要引入 System.Threading 命名空间。

    取消操作更加简单,代码如下:

            private void btn_Cancel_Click(object sender, EventArgs e)
            {
                if (_cts != null)
                    _cts.Cancel();
            }    

    这样我们就完成了在winform中使用多线程的例子,同时该例子支持取消操作。完整代码如下:

    using System;
    using System.Threading;
    using System.Windows.Forms;
    namespace WinformApp
    {
        public partial class Form1 : Form
        {
            private CancellationTokenSource _cts;
            public Form1()
            {
                InitializeComponent();
            }
            /// <summary>
            /// 从1累加到指定的值,为了让该方法支持取消操作所以需要CancellationToken参数
            /// </summary>
            /// <param name="countTo">累加到的指定值</param>
            /// <param name="ct">取消凭证</param>
            private void CountTo(int countTo) {
                int sum = 0;
                for (; countTo > 0; countTo--) {
                    if (ct.IsCancellationRequested) {
                        break;
                    }
                    sum += countTo;
                    //Invoke方法用于获得创建lbl_Status的线程所在的上下文
                    this.Invoke(new Action(()=>lbl_Status.Text = sum.ToString()));
    
                    Thread.Sleep(200);
                }
            }
            private void btn_Count_Click(object sender, EventArgs e)
            {
                _cts = new CancellationTokenSource();
                ThreadPool.QueueUserWorkItem(state=>CountTo(int.Parse(txt_CountTo.Text)));
            }
            private void btn_Cancel_Click(object sender, EventArgs e)
            {
                if (_cts != null)
                    _cts.Cancel();
            }
            private void btn_Pause_Click(object sender, EventArgs e)
            {
            }
        }
    }

    解决跨线程访问的问题

    主要有两个方案:

    1、关闭跨线程检查。

    2、通过委托的方式,在控件的线程上执行。

    具体的代码如下:

    using System;
    using System.Threading;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form2 : Form
        {
            public Form2()
            {
                InitializeComponent();
                //方法一:不进行跨线程安全检查  
                CheckForIllegalCrossThreadCalls = false;
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                Thread th1 = new Thread(new ThreadStart(CalNum));
                th1.Start();  
            }
    
            private void CalNum()
            {
                SetCalResult(DateTime.Now.Second);
            }
    
            //方法二:检查是否跨线程,然后将方法加入委托,调用委托  
            public delegate void SetTextHandler(int result);
            private void SetCalResult(int result)
            {
                if (label2.InvokeRequired == true)
                {
                    SetTextHandler set = new SetTextHandler(SetCalResult);//委托的方法参数应和SetCalResult一致  
                    label2.Invoke(set, new object[] { result }); //此方法第二参数用于传入方法,代替形参result  
                }
                else
                {
                    label2.Text = result.ToString();
                }
            }
        }
    }

    改进

    在我的Winform程序中,子线程涉及到对多个控件的更改,于是封装了一下,我这里使用的是拓展方法,只有在.net 3.5上才能支持,如果是.net2.0的环境,需要添加
    namespace System.Runtime.CompilerServices
    {
        [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
        public class ExtensionAttribute : Attribute { }
    }

    封装如下:

    using System.Threading;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
       public static class Class1
        {
    /// <summary>
            /// 跨线程访问控件 在控件上执行委托
            /// </summary>
            /// <param name="control">控件</param>
            /// <param name="method">执行的委托</param>
            public static void CrossThreadCalls(this Control control, ThreadStart method)
            {
                if (!control.IsHandleCreated || control.IsDisposed || control.Disposing)
                {
                    return;
                }
    
                if (method == null)
                {
                    return;
                }
    
                if (control.InvokeRequired)
                {
                    control.Invoke(method, null);
                }
                else
                {
                    method();
                }
            }
        }
    }               

    线程池是不可控制的.

  • 相关阅读:
    黑马程序员JAVA基础Java 集合之Collections 工具类
    黑马程序员JAVA基础Java 集合之Set 接口
    黑马程序员JAVA基础Java 集合之List 集合
    通过回车来完成数据的提交
    BizTalk中多个子记录映射解决办法
    框架的展开与关闭
    一个有用的触发器
    SQL 中分隔字符串的获取
    未保存离开页面要给出提示
    常用的一些javascript小技巧 很实用
  • 原文地址:https://www.cnblogs.com/feiyuhuo/p/5272937.html
Copyright © 2020-2023  润新知