C# 根据BackgroundWorker异步模型和ProgressBar控件,自定义进度条控件

简介:

C# 根据BackgroundWorker异步模型和ProgressBar控件,自定义进度条控件

前言
程序开发过程中,难免会有的业务逻辑,或者算法之类产生让人能够感知的耗时操作,例如循环中对复杂逻辑处理;获取数据库百万乃至千万级数据;http请求的时候等......
用户在使用UI操作并不知道程序的内部处理,从而误操作导致程序无响应,关闭程序等待影响体验的情况,因此,在等待过程中提供友好的等待提示是有必要的,接下来
我们一起封装一个自定义进度条控件!

主要使用技术(C#相关)
BackgroundWoker异步模型
ProgressBar控件
泛型
定时器 System.Timers.Timer
自定义控件开发
项目解决方案

BackgroundworkerEx : 自定义进度条控件工程
Test : 调用BackgroundworkerEx的工程(只是展示如何调用)
处理控件样式

新建一个ProgressbarEx名称的 用户控件
添加Labal控件(lblTips),用于展示进度条显示的信息状态
添加一个PictureBox控件(PicStop),充当关闭按钮,用于获取用户点击事件,触发关闭/终止进度条
添加进度条ProgressBar控件(MainProgressBar)
处理代码如下:
进度条样式为"不断循环",并且速度为50
该自定义用户控件不展示在任务栏中
图片控件被点击事件------>设置当前属性IsStop=true,指示过程终止;
TipMessage属性,用于设置进度条的信息
SetProgressValue(int value) 设置进度条的Value属性,使得在ProgressBarStyle.Marquee样式中动画平滑
MouseDown/MouseUp/MouseMove这三个事件是用于拖动无边框的用户控件(代码就不贴了)
public ProgressbarEx()
{

InitializeComponent();

MainProgressBar.Style = ProgressBarStyle.Marquee;
MainProgressBar.MarqueeAnimationSpeed = 50;

this.ShowInTaskbar = false;

PicStop.Click += (s, eve) =>
{
IsStop = true;
};

this.MouseDown += CusProgressForm_MouseDown;
this.MouseUp += CusProgressForm_MouseUp;
this.MouseMove += CusProgressForm_MouseMove;

}

///
/// Need Stop ?
///
public bool IsStop { get; private set; } = false;

///
/// TipMessage
///
public string TipMessage { get; set; }

///
/// TipMessage
///
public string TipMessage
{

get
{
return lblTips.Text;
}
set
{

lblTips.Text = value;
}

}

///
/// Set ProgressBar value ,which makes ProgressBar smooth
///
///
public void SetProgressValue(int value)
{

if (MainProgressBar.Value == 100) MainProgressBar.Value = 0;

MainProgressBar.Value += value;

}

到现在,这个自定义进度条控件的样式基本完成了.

功能逻辑处理
运行前所需
定义BackgroundWorkerEx泛型类,并且继承于 IDisposable
释放资源;

     /// <summary>
    /// Dispose
    /// </summary>
    public void Dispose()
    {
        try
        {
            DoWork = null;
            RunWorkCompleted = null;
            WorkStoped = null;

            _mWorkerThread = null;
            _mWorker.Dispose();
            _mWorker = null;
            _mTimer = null;
        }
        catch (Exception){}
    }

T用与异步处理的时候,传递T类型
因为我们是通过.Net 的 BackgroundWorker异步模型来做的,所以我们理所当然定义相关的事件:
异步开始
异步完成
加上我们自定义扩展的异步停止
......报告进度事件在此进度条样式中并不需要
我们先定义这四个事件所用到的参数,因为在BackgroundWorkerEx泛型类中,我们还是使用BackgroundWorker来处理异步过程,因此我们定义的参数泛型类需要继承原来的参数类型,并且在传输传递中,将原生BackgroundWorker的Argument,Result属性转成全局的泛型T,这样我们在外部调用的时候,拿到的返回结果就是我们传入到BackgroundWorkerEx泛型类中的T类型,而不需要使用as进行转换; 注:因为原生没有停止相关事件,所以自定义异步停止的事件参数使用的是DoWorkEventArgs

public class DoWorkEventArgs<T> : DoWorkEventArgs
{
    public new T Argument { get; set; }
    public new T Result { get; set; }
    public DoWorkEventArgs(object argument) : base(argument)
    {
        Argument = (T)argument;
    }
}
public class RunWorkerCompletedEventArgs<T> : RunWorkerCompletedEventArgs
{
    public new T Result { get; set; }
    public RunWorkerCompletedEventArgs(object result, Exception error, bool cancelled) : base(result, error, cancelled)
    {
        Result = (T)result;
    }
}

接着我们需要去定义事件,参数使用以上定义的泛型类

public delegate void DoWorkEventHandler(DoWorkEventArgs<T> Argument);
    /// <summary>
    /// StartAsync
    /// </summary>
    public event DoWorkEventHandler DoWork;

    public delegate void StopEventHandler(DoWorkEventArgs<T> Argument);
    /// <summary>
    /// StopAsync
    /// </summary>
    public event StopEventHandler WorkStoped;

    public delegate void RunWorkCompletedEventHandler(RunWorkerCompletedEventArgs<T> Argument);
    /// <summary>
    /// FinishAsync
    /// </summary>
    public event RunWorkCompletedEventHandler RunWorkCompleted;

定义全局的字段
private BackgroundWorker _mWorker = null;异步操作必要;
private T _mWorkArg = default(T);操作传递进来的参数类并且返回到外部
private Timer _mTimer; 定时器检测自定义进度条控件属性IsStop是否为true,并且动态修改进度条消息
private Thread _mWorkerThread = null;异步操作在该线程中,终止时调用About()抛出ThreadAbortException异常,用于标记当前是停止而不是完成状态
private int _miWorkerStartDateSecond = 0; 异步消耗时间(非必要)
private int _miShowProgressCount = 0; 动态显示"."的个数(非必要)
private ProgressbarEx _mfrmProgressForm = null; 自定义进度条控件实例

    /// <summary>
    /// .Net  BackgroundWorker
    /// </summary>
    private BackgroundWorker _mWorker = null;

    /// <summary>
    /// Whole Para
    /// </summary>
    private T _mWorkArg = default(T);

    /// <summary>
    /// Timer
    /// </summary>
    private Timer _mTimer = null;

    /// <summary>
    /// WorkingThread
    /// </summary>
    private Thread _mWorkerThread = null;

    /// <summary>
    /// Async time sec
    /// </summary>
    private int _miWorkerStartDateSecond = 0;

    /// <summary>
    /// Async time dot
    /// </summary>
    private int _miShowProgressCount = 0;

    /// <summary>
    /// ProgressbarEx
    /// </summary
    private ProgressbarEx _mfrmProgressForm = null;

定义全局属性
IsBusy 返回_mWorker的工作忙碌是否
ProgressTip 自定义进度条控件显示内容

/// <summary>
    /// Express Busy
    /// </summary>
    public bool IsBusy
    {
        get
        {
            if (_mWorker != null)
            {
                return _mWorker.IsBusy;
            }
            return false;
        }
    }

    /// <summary>
    /// 进度条提示 默认: 正在加载数据,请稍后[{0}]{1}
    /// </summary>
    public string ProgressTip { get; set; } = "Elapsed Time[{0}]{1}";

到现在,我们已经将必要的字段,属性,样式都处理完成!!!  接下来我们就要实现方法

方法实现
异步工作事件,用法与BackgroundWorker一致,
如果调用处没有注册DoWork事件,则直接返回
将接受到的参数创建成泛型参数类
开线程,将异步操作放在该线程中操作,注意设置线程的IsBackground=true,防止主进程意外退出,线程还在处理
循环直到线程结束
e.Result = Argument.Result;将结果赋予Result,在停止或者完成事件中可以获取到结果

    /// <summary>
    /// Working
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Worker_DoWork(object sender, DoWorkEventArgs e)
    {
        if (DoWork == null)
        {
            e.Cancel = true;
            return;
        }

        DoWorkEventArgs<T> Argument = new DoWorkEventArgs<T>(e.Argument);

        try
        {
            if (_mWorkerThread != null && _mWorkerThread.IsAlive)
            {
                _mWorkerThread.Abort();
            }
        }
        catch (Exception)
        {
            Thread.Sleep(50);
        }

        _mWorkerThread = new Thread(a =>
        {
            try
            {
                DoWork?.Invoke(a as DoWorkEventArgs<T>);
            }
            catch (Exception)
            {

            }
        });

        _mWorkerThread.IsBackground = true;
        _mWorkerThread.Start(Argument);

        //Maybe cpu do not start thread
        Thread.Sleep(20);

        //Wait.....
        while (_mWorkerThread.IsAlive)
        {
            Thread.Sleep(50);
        }
        e.Result = Argument.Result;
    }

异步完成/停止
当线程停止抛出异常(catch但是不处理)/线程完成时会进入异步完成事件

完成后,将自定义进度条控件实例关闭,释放
将全局的BackgroundWorker实例_mWorker相关事件取消注册,并且检查线程情况
感觉线程情况,如果线程状态为ThreadState.Aborted意味着线程被停止了,调用停止事件,否则调用完成事件

  /// <summary>
  /// Completed
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void Worker_RunWorkCompleted(object sender, RunWorkerCompletedEventArgs e)
  {
      try
      {
          if (_mfrmProgressForm != null)
          {
            _mfrmProgressForm.Close();
              _mfrmProgressForm.Dispose();
            _mfrmProgressForm = null;
          }

            if (_mWorker != null)
            {
                _mWorker.DoWork -= Worker_DoWork;
                _mWorker.RunWorkerCompleted -= Worker_RunWorkCompleted;

                try
                {
                    if (_mWorkerThread != null && _mWorkerThread.IsAlive) _mWorkerThread.Abort();
                }
                catch (Exception) { }
            }

          //In timer, When stop progress will make thread throw AbortException
          if (_mWorkerThread != null && _mWorkerThread.ThreadState == ThreadState.Aborted)
        {
              WorkStoped?.Invoke(new DoWorkEventArgs<T>(_mWorkArg));
          }
          else
          {
              RunWorkCompleted?.Invoke(new RunWorkerCompletedEventArgs<T>(e.Result, e.Error, e.Cancelled));
          }
      }
      catch (Exception ex)
      {
          throw ex;
      }
  }

线程开始
检查消息提醒内容 , {0}{1}同于显示异步耗时和".."的个数
在定时器执行方法中,检查_mfrmProgressForm.IsStop是否为true,这个属性标志是否被停止;true则抛出异常
_mfrmProgressForm不为Null则不断修改当前的内容提醒,友好化,实际可以按需处理

      /// <summary>
    /// Timer Start 
    /// </summary>
    private void StartTimer()
    {
        //Check user ProgressTip
        if (!ProgressTip.Contains("{0}"))
        {
            ProgressTip += "...Elapsed Time{0}{1}";
        }

        if (_mTimer != null) return;

        //On one sec 
        _mTimer = new Timer(1000);
        _mTimer.Elapsed += (s, e) =>
        {
            //progress and it's stop flag (picture stop)||  this stop flag
            if (_mfrmProgressForm != null && _mfrmProgressForm.IsStop)
            {
                if (_mWorker != null)
                {
                    try
                    {
                        if (_mWorkerThread != null && _mWorkerThread.IsAlive)
                        {
                            if (_mTimer != null && _mTimer.Enabled)
                            {
                                _mTimer.Stop();
                                _mTimer = null;
                            }
                            _mWorkerThread.Abort();
                        }
                    }
                    catch (Exception) { }
                }
            }

            if (_mfrmProgressForm != null)
            {
                //Callback 
                _mfrmProgressForm.Invoke(new Action<DateTime>(elapsedtime =>
                {
                    DateTime sTime = elapsedtime;

                    //worked time
                    _miWorkerStartDateSecond++;
                    if (_mfrmProgressForm != null)
                    {
                        _mfrmProgressForm.SetProgressValue(_miWorkerStartDateSecond);
                    }

                    //.....count
                    _miShowProgressCount++;

                    if (_miShowProgressCount > 6)
                    {
                        _miShowProgressCount = 1;
                    }

                    string[] strs = new string[_miShowProgressCount];

                    string ProgressStr = string.Join(".", strs);

                    string ProgressText = string.Format(ProgressTip, _miWorkerStartDateSecond, ProgressStr);

                    if (_mfrmProgressForm != null)
                    {
                        _mfrmProgressForm.TipMessage = ProgressText;
                    }
                }), e.SignalTime);
            }
        };

        if (!_mTimer.Enabled)
        {
            _mTimer.Start();
        }
    }

最后一步:异步开始 与BackgroundWorker用法一致,只是在最后开始了定时器和进度条控件而已
///

    /// Start AsyncWorl
    /// </summary>
    /// <param name="Para"></param>
    public void AsyncStart(T Para)
    {
        //if workeven is  null ,express user do not regist event
        if (DoWork == null)
        {
            return;
        }

        _miWorkerStartDateSecond = 0;
        _miShowProgressCount = 0;

        //init
        if (_mWorker != null && _mWorker.IsBusy)
        {
            _mWorker.CancelAsync();
            _mWorker = null;
        }

        _mWorker = new BackgroundWorker();

        //create progressbar
        _mfrmProgressForm = new ProgressbarEx();

        //add event
        _mWorker.DoWork += Worker_DoWork;
        _mWorker.RunWorkerCompleted += Worker_RunWorkCompleted;

        _mWorker.WorkerReportsProgress = true;
        _mWorker.WorkerSupportsCancellation = true;

        //Set Whole Para
        _mWorkArg = Para;

        _mWorker.RunWorkerAsync(Para);
        //Start timer
        StartTimer();

        _mfrmProgressForm.StartPosition = FormStartPosition.CenterParent;
        _mfrmProgressForm.ShowDialog();
    }

到这里,整个的进度条控件已经完成了!

调用
定义一个参数类

/// <summary>
/// Para Class
/// </summary>
public class ParaArg
{
    public DataTable Data { get; set; }
    public string Msg { get; set; }
    public Exception Ex { get; set; }
}

定义全局的帮助类BackgroundWorkerEx workHelper = null;
调用

            if (workHelper != null || (workHelper != null && workHelper.IsBusy))
            {
                workHelper.Dispose();
                workHelper = null;
            }
            if (workHelper == null)
            {
                workHelper = new BackgroundWorkerEx<ParaArg>();
            }

            workHelper.DoWork += (eve) =>
            {
                ParaArg args = eve.Argument;

                try
                { 
                    //ToDo  like Thread.Sleep(20000);
                    Thread.Sleep(10000);
                    args.Msg = "...this is bussiness code result";
                    throw new Exception("");
                }
                catch (Exception ex)
                {
                    args.Ex = ex;
                }
                finally
                {
                    eve.Result = args;
                }

            };
            workHelper.RunWorkCompleted += (eve) =>
            {
                if (eve.Error != null)
                {
                    //get .net backgroundworker exception;
                    //handle this exception;
                    //return ?
                }

                //get your para result
                ParaArg x = eve.Result;
             
                if (x.Ex != null)
                {
                    //get your bussiness exception;
                    //handle this exception;
                    //return ?
                }

                //finially get your need;
                //MayBe to do some UI hanlde and bussiness logical
                string sReusltMsg = x.Msg;
            };

            workHelper.WorkStoped += (eve) =>
            { 
                //if stoped ! it means no error;
                //just get what you want; 
                ParaArg x = eve.Result as ParaArg;

                btnBegin.Enabled = true;
            };

            //参数
            ParaArg arg = new ParaArg()
            {
                Msg = "Msg"
            };

            workHelper.AsyncStart(arg);

最后
其实不管是封装的过程,还是调用,可以说完全就是BackgroundWorker的方式,所以很多BackgroundWorker相关的地方我都没有很详细的去说明;只要看看这个异步模型,就能够很好理解!大家有空也可以实现以下,有问题也可以详细,我比较喜欢交流技术~~~

还有一点就是这个解决方案已经放上Github上了,欢迎大家拉下来用

GitHub地址 欢迎star/fork
原文地址https://www.cnblogs.com/Ligy97/p/12993155.html

相关文章
|
1月前
|
SQL 开发框架 .NET
C#一分钟浅谈:数据绑定与数据源控件
在Web开发中,数据绑定和数据源控件是实现动态网页的关键技术。本文从基础概念入手,详细讲解数据绑定的原理及其在ASP.NET中的应用,并介绍常见数据绑定方式:手动绑定和自动绑定。接着,文章重点介绍了ASP.NET中的数据源控件,如`SqlDataSource`、`ObjectDataSource`、`XmlDataSource`和`LinqDataSource`,并通过具体示例演示如何使用`SqlDataSource`和`GridView`进行数据绑定。最后,还列举了一些常见问题及其解决办法,帮助读者更好地理解和应用这些技术。
66 4
|
3月前
|
C#
|
16天前
|
C# UED SEO
C# 异步方法async / await任务超时处理
通过使用 `Task.WhenAny`和 `Task.Delay`方法,您可以在C#中有效地实现异步任务的超时处理机制。这种方法允许您在指定时间内等待任务完成,并在任务超时时采取适当的措施,如抛出异常或执行备用操作。希望本文提供的详细解释和代码示例能帮助您在实际项目中更好地处理异步任务超时问题,提升应用程序的可靠性和用户体验。
44 3
|
28天前
|
C# Python
使用wxpython开发跨平台桌面应用,对wxpython控件实现类似C#扩展函数处理的探究
【10月更文挑战第30天】使用 `wxPython` 开发跨平台桌面应用时,可以通过创建辅助类来模拟 C# 扩展函数的功能。具体步骤包括:1. 创建辅助类 `WxWidgetHelpers`;2. 在该类中定义静态方法,如 `set_button_color`;3. 在应用中调用这些方法。这种方法提高了代码的可读性和可维护性,无需修改 `wxPython` 库即可为控件添加自定义功能。但需要注意显式调用方法和避免命名冲突。
|
5月前
|
C# C++
C# 自定义时间进度条
本文作者通过参考leslie_xin的一篇文章,成功创建了一个自定义的WinForms控件——时间进度条,该控件带有时间刻度和多种可定制的属性,如颜色、时间间隔等。作者在控件中加入了开始和结束时间,以及自适应的时间刻度间隔。控件能根据设置显示时间标签,并提供了事件处理,如值改变时的触发。代码中包含了计算时间刻度、绘制刻度线和时间标签的逻辑。作者强调了避免循环调用事件、使用OnXXX()形式的事件处理函数以及注意自定义控件中的属性和事件设计。
139 7
|
3月前
|
开发框架 .NET 编译器
总结一下 C# 如何自定义特性 Attribute 并进行应用
总结一下 C# 如何自定义特性 Attribute 并进行应用
|
3月前
|
API C# 数据库
SemanticKernel/C#:实现接口,接入本地嵌入模型
SemanticKernel/C#:实现接口,接入本地嵌入模型
86 1
|
3月前
|
C#
C# async await 异步执行方法
C# async await 异步执行方法
54 0
|
3月前
|
API C#
SemanticKernel/C#:使用Ollama中的对话模型与嵌入模型用于本地离线场景
SemanticKernel/C#:使用Ollama中的对话模型与嵌入模型用于本地离线场景
104 0
|
3月前
|
C# 开发者 Windows
WPF/C#:ProgressBar的基本使用
WPF/C#:ProgressBar的基本使用
95 0