ProgressChanged回调中的BackgroundWorker和UserState问题

时间:2011-01-21 16:02:24

标签: c# multithreading backgroundworker

似乎我不理解后台工作者中的用户概念。 我正在处理我正在处理的应用程序中的问题,我需要解释为什么发生了一些我没想到的事情。

我已经构建了一个演示应用程序来更简单地重现问题:

public class Tester
{
    private BackgroundWorker _worker = new BackgroundWorker();

    public void performTest()
    {
        Tester tester = new Tester();
        tester.crunchSomeNumbers((obj, arg) =>
        {
            WorkerArgument userState = arg.UserState as WorkerArgument;
            Console.WriteLine(string.Format("Progress: {0}; Calculation result: {1}", arg.ProgressPercentage, userState.CalculationResult));
        });
    }

    public void crunchSomeNumbers(Action<object,ProgressChangedEventArgs> onProgressChanged)
    {
        _worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        _worker.ProgressChanged += new ProgressChangedEventHandler(onProgressChanged);
        _worker.WorkerReportsProgress = true;
        _worker.RunWorkerAsync(new WorkerArgument { CalculationResult=-1, BaseNumber = 10 });
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        WorkerArgument arg = e.Argument as WorkerArgument;

        for (int i = 0; i < 10; i++)
        {
            // calculate total with basenumber
            double result = arg.BaseNumber * (i * 10);
            arg.CalculationResult = result;
            worker.ReportProgress(i * 10, arg);
        }
    }

    public class WorkerArgument
    {
        public int BaseNumber { get; set; }
        public double CalculationResult { get; set; }
    }
}

如果您在控制台App中运行此代码:

class Program
{
    static void Main(string[] args)
    {
        Tester tester = new Tester();
        tester.performTest();

        Console.ReadLine();
    }
}

结果如下:

http://img684.imageshack.us/img684/1509/bgwproblem.png

我不明白为什么计算结果总是一样的,你可以清楚地看到在DoWork方法的forloop中每次计算运行时它应该是不同的。

4 个答案:

答案 0 :(得分:2)

您假设事件是在循环的下一次迭代之前引发并执行的。不幸的是,这是不正确的。

正在发生的事情是你的for循环在第一个事件执行之前完成。因此,在调用Console.WriteLine之前,userState.CalculationResult位于900。如果您将顶部改为

,则为
for (int i = 0; < 1000000; i++)

您应该看到数字有所增加,但在所有事件执行之前它会达到最大数量。

另一种方法是在调用worker.ReportProgress之前放入Console.WriteLine。您将看到for循环的完成顺序与事件报告不同。它不会像第一个事件代码输出那样完整,因为Console.WriteLine是一个非常慢的调用,并且大大减慢了for循环的执行速度。

多线程要记住的一个令人兴奋的问题是调用事件是非阻塞的。

答案 1 :(得分:1)

有两个问题:

1)循环在第一个事件处理程序执行之前执行    如果在调用ReportProgress后冻结后台工作线程执行,则可以看到它。

arg.CalculationResult = result;
worker.ReportProgress(i * 10, arg);
Thread.Sleep(500);

2)您正在使用WorkerArgument的一个实例 - 因此,当执行事件处理程序时,它具有当前的WorkerArgument值,而不是引发事件时的值。在你举起事件时,太看到这只是传递新的参数实例。

worker.ReportProgress(i * 10, new WorkerArgument(){ CalculationResult = result });

答案 2 :(得分:0)

我无法彻底发现问题,但可能是关于匿名方法和变量固定。尝试通过重写每个匿名方法或lambda作为正确的方法来缩小范围,并查看问题是否仍然存在。

答案 3 :(得分:0)

您需要调用传入的工作人员:

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker this_worker = sender as BackgroundWorker;
    WorkerArgument arg = e.Argument as WorkerArgument;

    for (int i = 0; i < 10; i++)
    {
        // calculate total with basenumber
        double result = arg.BaseNumber * (i * 10);
        arg.CalculationResult = result;
        this_worker.ReportProgress(i * 10, arg);
    }
}