65.9K
CodeProject 正在变化。 阅读更多。
Home

BackgroundWorker 辅助工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (20投票s)

2012 年 1 月 23 日

CPOL

2分钟阅读

viewsIcon

43183

downloadIcon

4445

一个可以缩短您使用 BackgroundWorker 时间的类,并处理一些与其相关的逻辑。

引言

BackgroundWorker 类用于管理在后台线程中执行的工作,并将进度报告给 UI,从而避免跨线程问题。 实现所有 BackgroundWorker 逻辑有点繁琐,并且会占用您的代码空间。

在本文中,我介绍了一个包装类,它可以处理您通常需要做的一些工作。 它适用于需要执行一组独立任务的情况。

一个例子可能是向多个收件人发送邮件或处理一组图像。

Using the Code

BWHelper 类聚合了应该执行的操作。 它处理 BackgroundWorker.DoWork 并检查用户是否决定使用并行或顺序执行。 然后它执行所有 Action,在每次执行之前检查是否请求了取消。

该类还计算完成的百分比和可能的剩余时间(基于已完成任务的平均执行时间)。

public class BWHelper
{
    private IEnumerable<Action> toDo;
    private DateTime startTime;
    
    private bool isParallel = false;
    private BackgroundWorker worker;

    private ValueMonitor<int> percentageProgress = new ValueMonitor<int>(0);
    private ValueMonitor<TimeSpan> timeLeft = new ValueMonitor<TimeSpan>(TimeSpan.MaxValue);

    public void SetActionsTodo( IEnumerable<Action> toDoActions)
    {
        toDo = toDoActions;
    }

    public bool IsParallel
    {
        get { return isParallel; }
        set { isParallel = value; }
    }

    public IValueMonitor<TimeSpan> TimeLeft { get { return timeLeft; } }

    public BWHelper(BackgroundWorker aWorker)
    {
        worker = aWorker;
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;
        percentageProgress.ValueChanged += 
                   new ValueChangedDelegate<int>(percentageProgress_ValueChanged);

        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    }

    public BWHelper(IEnumerable<Action> actionsToDo, BackgroundWorker aWorker):this(aWorker)
    {
        toDo = actionsToDo;
    }

    public int Total
    {
        get
        {
            if (toDo == null) return 0;
            return toDo.Count();
        }
    }

    private void percentageProgress_ValueChanged(int oldValue, int newValue)
    {
        worker.ReportProgress(newValue);
    }

    private void  worker_DoWork(object sender, DoWorkEventArgs e)
    {
        if (toDo == null)
            throw new InvalidOperationException("You must provide actions to execute");
        int total = toDo.Count();
        startTime = DateTime.Now;
        int current = 0;
        if (isParallel == false)
        {
            foreach (var next in toDo)
            {
                next();
                current++;
                if (worker.CancellationPending == true) return;
                percentageProgress.Value = (int)((double)current / (double)total * 100.0);
                double passedMs = (DateTime.Now - startTime).TotalMilliseconds;
                double oneUnitMs = passedMs / current;
                double leftMs = (total - current) * oneUnitMs;
                timeLeft.Value = TimeSpan.FromMilliseconds(leftMs);
            }            
        }
        else
        {
            
            Parallel.For(0, total - 1,
                (index, loopstate) => 
                    { 

                        toDo.ElementAt(index)();
                        if (worker.CancellationPending == true) loopstate.Stop();
                        Interlocked.Increment(ref current);

                        percentageProgress.Value = (int)((double)current / (double)total * 100.0);
                        double passedMs = (DateTime.Now - startTime).TotalMilliseconds;
                        double oneUnitMs = passedMs / current;
                        double leftMs = (total - current) * oneUnitMs;
                        timeLeft.Value = TimeSpan.FromMilliseconds(leftMs);
                    }
                );
        }
    }
} 

ValueMonitor 类被密集地用于 intTimeSpan 值类型。 请注意,由于 TimeLeft 变化是通过 ValueMonitor.ValueChanged 事件发出的,而不是使用 BackgroundWorker 来实现,因此您应该自己实现跨线程逻辑。 在示例项目中,为了简单起见,并且因为我比较懒,CheckForIllegelCrossThreadCalls 属性设置为 true

示例项目是一个 Windows Forms 应用程序,它执行“有用”的任务,每个任务需要 200 毫秒。 它还显示了可能的剩余时间。 您也可以选择并行或顺序执行。

当按下 开始 按钮时,将执行以下代码

List<Action> actions = new List<Action>();
for (int i = 0; i< 100; i++)
    actions.Add( () => Thread.Sleep(200) );

helper.SetActionsTodo(actions);
helper.IsParallel = checkBoxUseParallel.Checked;
backgroundWorker.RunWorkerAsync();

BWHelper 中获得的优势是

  • 百分比计算集中在一个地方。 您也不必担心 ReportProgress 方法。
  • 测量平均剩余时间。 您可以处理 BWHelper.TimeLeft.ValueChanged 事件以在 UI 中显示此信息。
  • 易于在顺序和并行操作执行之间切换。
  • 取消逻辑也不是您的任务。
  • 构造函数中将 WorkerReportsProgressWorkerSupportsCancellation 属性设置为 true

虽然我们获得了这些优势,但应该指出这个类无法提供什么

  • 当您的 Actions 依赖于彼此的结果时,BWHelper 不适用。
  • 请记住,如果您的某些操作花费的时间太长,则取消可能也需要很长时间。

历史

  • 2012 年 1 月 22 日 - 首次发布
© . All rights reserved.