Проблема с BackgroundWorker и UserState при обратном вызове ProgressChanged - PullRequest
2 голосов
/ 21 января 2011

Кажется, я не понимаю концепцию пользовательского состояния в фоновом режиме.Я столкнулся с проблемой в приложении, над которым я работаю, и мне нужно объяснить, почему происходит то, чего я не ожидал.

Я создал демонстрационное приложение, чтобы проще воспроизвести проблему:

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; }
    }
}

Если вы запустите этот код в консольном приложении:

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

        Console.ReadLine();
    }
}

Это результат:

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

Что я наденуЯ не понимаю, почему результат расчета всегда один и тот же, в то время как вы можете ясно видеть, что он должен отличаться при каждом прогоне вычисления в цикле forWork метода DoWork.

Ответы [ 4 ]

2 голосов
/ 21 января 2011

Вы предполагаете, что события генерируются и выполняются до следующей итерации цикла. К сожалению, это не правильно.

Что происходит, так это то, что ваш цикл for завершается до того, как будет выполнено первое событие. Таким образом, userState.CalculationResult находится на 900 перед вызовом Console.WriteLine. Вы, если вы поменяете на сверху на

for (int i = 0; < 1000000; i++)

Вы должны увидеть, что число увеличивается, но оно достигает максимального числа до того, как все события будут выполнены.

Другой способ - поместить Console.WriteLine перед вызовом worker.ReportProgress. Вы увидите, что порядок завершения цикла for отличается от отчета о событии. Он не будет настолько полным при первом выводе кода события, как Console.WriteLine - действительно медленный вызов и значительно замедляет выполнение цикла for.

Одна из захватывающих проблем, о которых следует помнить при многопоточности, - это не блокирование событий.

1 голос
/ 21 января 2011

Есть две проблемы:

1) Цикл выполняется перед первым обработчиком события Это можно увидеть, если вы остановите выполнение фонового рабочего потока после вызова ReportProgress.

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

2) Вы используете один экземпляр WorkerArgument - поэтому, когда обработчик события выполняется, он имеет текущее значение WorkerArgument, а не то, которое было при возникновении события. Видите, это просто передать новый экземпляр аргумента, когда вы вызываете события.

worker.ReportProgress(i * 10, new WorkerArgument(){ CalculationResult = result });
0 голосов
/ 21 января 2011

Вы хотите позвонить работнику, который был передан:

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);
    }
}
0 голосов
/ 21 января 2011

Я не могу сразу определить проблему, но, вероятно, речь идет об анонимных методах и закреплении переменных. Попробуйте сузить его, переписав каждый анонимный метод или лямбду как правильный метод, и посмотрите, сохраняется ли проблема.

...