委托+线程 winform下超简单实时进度条控件的实现

来源:互联网 发布:json数组去重 编辑:程序博客网 时间:2024/06/08 00:51


由于一直以来都是在做ASP.NET 对于多线程这一块涉猎不深 觉得趁着入职之前好好练习一下多线程编程 多线程最简单直观的例子估计就是进度条了 同时由于还涉及到UI线程 更加可以了解一下winform编程方面一些原则

一般来讲 进度条可以有两种做法 一种是两个线程 一个UI线程 这是必须的 另一个则是耗时操作进程 同时通过回调 实时通知UI线度变化 UI线程接受到这些进度变化来进行相应的UI更新 但是这样子两者的耦合过高 很难分离出来一个通用的控件(因为每个耗时操作中必须包含一个委托 该委托来通知UI线程 我这边有进度发生变化了 但是耗时操作时交给调用方去实现的 所以相当于是调用方知道了太多的东西)

另一种做法就是用三个线程 即UI线程本身 工作线程 以及监听线程 监听线程实时获取工作线程的执行的情况 并负责更新UI  这里面有一个很大的问题就是如何实时获取工作线程的进度情况 如果不能用委托 这里就提出一种没有被验证是否可靠的但是现在来说算是可行的方案 即引用类型参数

由于引用类型参数在传值的时候传递的是本身 因此所有针对他的变化都可以被外部获取 但是为了保证该变量的安全 设计的时候将该变量作为控件 的私有字段 并对外开放只读属性 这样就保证了该变量不会被非工作线程所改变

先上调用代码 来看下我们的进度条的接口 从调用方来看十分简单

 

private void Start_Click(object sender, EventArgs e)        {            this.myProcessBar1.Task = this.Task;            this.myProcessBar1.Run();//开始工作线程 同时开始监听工作进度        }        //某个耗时操作 参数即当前进度        public void Task(ref float percentage)        {            int i = 0;            while (i < int.MaxValue )            {                i++;                percentage = i / (float)int.MaxValue;            }        }        private void button1_Click(object sender, EventArgs e)        {            this.myProcessBar1.Stop();//暂停进度条 同时也暂停工作线程        }        private void button2_Click(object sender, EventArgs e)        {            this.myProcessBar1.Resume();//继续进度条 同时继续工作线程        }        private void button3_Click(object sender, EventArgs e)        {            this.myProcessBar1.Abort();//终止工作进程        }        private void button4_Click(object sender, EventArgs e)        {            this.myProcessBar1.Run();//这边位重新启动进度条        }

 

下面是进度条本身的代码 这边用一个picturebox作为进度条容器

 

 public partial class MyProcessBar : UserControl    {        public delegate void TaskHandler(ref float percentage);        private Thread _monitorThread = null;        private Thread _workerThread = null;        private float _percentage = 0f;        private MyProcessBarStatue _currentStatue = MyProcessBarStatue.UnStarted;        private TaskHandler _task = null;        public MyProcessBar()        {            InitializeComponent();            this._monitorThread = new Thread(new ThreadStart(Monitor));//监听线程 只构造一次        }        /// <summary>        /// 任务委托        /// 多次对该属性赋值会造成不必要的浪费        /// </summary>        public TaskHandler Task        {            set            {                //任务线程 通过传入引用类型参数获得实时的进度变更                 this._workerThread = new Thread(() => value(ref _percentage));                this._task = value;            }        }        /// <summary>        /// 外部也可以实时获取当前任务进度        /// </summary>        public float Percentage        {            get            {                return this._percentage;            }        }        /// <summary>        /// 外部实时获取当前任务状态        /// </summary>        public MyProcessBarStatue CurrentStatue        {            get            {                return this._currentStatue;            }        }        public void Run()        {            if (this._workerThread == null)            {                throw new NullReferenceException("Task委托不能为空!");            }            //初次启动 需要同时启动工作线程和监听线程            if (this._currentStatue == MyProcessBarStatue.UnStarted)            {                _monitorThread.IsBackground = true;                _monitorThread.Start();                _workerThread.IsBackground = true;                _workerThread.Start();                this._currentStatue = MyProcessBarStatue.Running;            }            //被终止后第二次启动 只需要重启工作线程就可以             else if (this._currentStatue == MyProcessBarStatue.Aborted)            {                this._currentStatue = MyProcessBarStatue.Running;                //若原工作线程已经终止 则重新初始化工作线程                if (this._workerThread.ThreadState == ThreadState.Aborted)                {                    this._workerThread = null;                    this._workerThread = new Thread(() => _task(ref _percentage));                }                _workerThread.IsBackground = true;                _workerThread.Start();            }            else            {                throw new InvalidOperationException("已经开始的任务无法再次开始!");            }        }                public void Stop()        {            if (_workerThread == null)            {                throw new NullReferenceException("Task委托不能为空!");            }            if (this._currentStatue == MyProcessBarStatue.Aborted)            {                throw new InvalidOperationException("已经终止的操作无法暂停!");            }            if (this._currentStatue != MyProcessBarStatue.Suspended)            {                _workerThread.Suspend();                _monitorThread.Suspend();                this._currentStatue = MyProcessBarStatue.Suspended;            }                    }        public void Resume()        {            if (_workerThread == null)            {                throw new NullReferenceException("Task委托不能为空!");            }            if (this._currentStatue == MyProcessBarStatue.Aborted)            {                throw new InvalidOperationException("已经终止的操作无法继续!");            }            if (this._currentStatue == MyProcessBarStatue.Suspended)            {                _monitorThread.Resume();                _workerThread.Resume();                this._currentStatue = MyProcessBarStatue.Running;            }                    }        public void Abort()        {            if (this._workerThread == null)            {                throw new NullReferenceException("Task委托不能为空!");            }            if (this._currentStatue != MyProcessBarStatue.Aborted)            {                //若之前为Suspended状态 则需要先Resume才可以终止工作线程                this.Resume();                _workerThread.Abort();                this._currentStatue = MyProcessBarStatue.Aborted;            }        }        private void Monitor()        {            //外层无限循环 监听标志变量的改变            while (true)            {                //当UI调用Start方法时监听线程首次启动并设置标志变量为Running                if (this._currentStatue == MyProcessBarStatue.Running)                {                    //在此循环中监听_percetage变量的改变情况 由于该变量作为引用参数传入Task委托 所以可以得到实时的进度                    while (this._percentage <= 1f)                    {                        //调用Draw方法更新UI                        using (var graphic = pictureBox1.CreateGraphics())                        {                            if (this.pictureBox1.InvokeRequired)                            {                                this.Invoke(new Action(() => Draw(this._percentage, graphic)));                            }                            else                            {                                this.Draw(this._percentage, graphic);                            }                        }                        Thread.Sleep(10);//重绘间隔为10ms                        //当发现标志变量改变为终止时跳出 回到最外层监听标志变量的循环                        if (this._currentStatue == MyProcessBarStatue.Aborted)                        {                            this._percentage = 0f;//将百分比重置为0                            if (this.pictureBox1.InvokeRequired)//将绘图区清空                            {                                this.Invoke(new Action(() => this.Clear()));                            }                            else                            {                                this.Clear();                            }                            break;                        }                    }                }            }        }        /// <summary>        /// 如果要更改进度条显示的形式 则继承当前类 重写Draw方法        /// </summary>        /// <param name="percentage"></param>        /// <param name="graphic"></param>        protected virtual void Draw(float percentage,Graphics graphic)        {            graphic.FillRectangle(Brushes.Black, 0f, 0f, _percentage * pictureBox1.Width, pictureBox1.Height);        }        private void Clear()        {            using (var graphic = pictureBox1.CreateGraphics())            {                graphic.Clear(Color.White);            }        }    }    //进度条状态枚举    public enum MyProcessBarStatue    {        UnStarted,        Running,        Suspended,        Aborted    }


总结 由于本身三个线程中只有一个线程在进行写的操作(工作线程)UI线程在被动等待 监听线程在监听工作线程然后更新UI 所以并不涉及数据同步的问题 虽然在绘图的时候会发生一些延迟 但如果我们为此锁住percentage变量也会造成对工作线程的影响 所以这边并没有用到LOCK语句 其次 线程三个状态之间的切换十分头疼 所以有时候自己写标志变量来代替线程本身的ThreadState属性是个比较好的主意 第三 由于每次重新启动进度条都设计工作线程的重新初始化 如果用户恶意多次点击start按钮 会造成不必要的资源浪费(线程操作时很消耗操作系统资源的) 而目前除了重新new一个Thread还没有一个比较好的办法让线程从头开始再次执行 所以只能用这样一种比较不优雅的方式 可以考虑用backgroundworker来代替 毕竟backgroundworker本身是也是一种线程池线程 对资源的利用更加高效