BackgroundWorker 辅助工具
一个可以缩短您使用 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
类被密集地用于 int
和 TimeSpan
值类型。 请注意,由于 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 中显示此信息。 - 易于在顺序和并行操作执行之间切换。
- 取消逻辑也不是您的任务。
- 构造函数中将
WorkerReportsProgress
和WorkerSupportsCancellation
属性设置为true
。
虽然我们获得了这些优势,但应该指出这个类无法提供什么
- 当您的 Actions 依赖于彼此的结果时,
BWHelper
不适用。 - 请记住,如果您的某些操作花费的时间太长,则取消可能也需要很长时间。
历史
- 2012 年 1 月 22 日 - 首次发布