BackgroundWorker и Dispatcher.Invoke

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

MyView.UpdateStatus( "Please wait" );
LongComputation();
MyView.UpdateStatus( "Ready" );

Это не работает, потому что (я думаю) LongComputation предотвращает обновление статуса.

Итак, я попытался сделать это:

BackgroundWorker worker = new BackgroundWorker();
MyView.UpdateStatus( "Please wait");
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
    LongComputation();
}
worker.RunWorkerAsync();
MyView.UpdateStatus( "Ready" );

Я надеялся, что дополнительный поток даст UpdateStatus возможность обновить текст статуса. Тоже не работает. Одна из причин заключается в том, что результат LongComputation отображается в форме Windows. Как только я помещаю LongComputation в BackgroundWorker, результат не отображается.

Итак, я попробовал в третий раз, используя текущий код:

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
    Dispatcher.Invoke(((Action)(() => Status.Text = args.Argument as string)));
};

worker.RunWorkerAsync(newStatus);

Я надеялся, что размещение обновления в другом потоке сработает. Но этого не произошло.

Что я могу сделать, чтобы убедиться, что статус отражает правильное состояние программы?


person Unplug    schedule 03.11.2013    source источник
comment
Возвращает ли LongComputation то, чего ждет пользовательский интерфейс?   -  person Jason    schedule 03.11.2013
comment
нет, он строит линейную диаграмму после некоторого сложного расчета.   -  person Unplug    schedule 03.11.2013


Ответы (2)


BackgroundWorker использует события RunWorkerCompleted и ReportProgress для обратной связи с основным потоком. RunWorkerCompleted должен делать то, что вам нужно, поскольку он будет выполняться в потоке пользовательского интерфейса, как только завершится фоновая работа.

        BackgroundWorker worker = new BackgroundWorker();

        worker.DoWork += delegate(object s, DoWorkEventArgs args)
        {
            LongComputation();
        };

        // RunWorkerCompleted will fire on the UI thread when the background process is complete
        worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
        {
            if (args.Error != null)
            {
                // an exception occurred on the background process, do some error handling
            }

            MyView.UpdateStatus("Ready");
        };

        MyView.UpdateStatus("Please wait");
        worker.RunWorkerAsync();

Кроме того, вы можете использовать RunWorkerCompleted для маршалинга результатов обратно в основной поток с помощью свойства Result DoWorkerEventArgs.

        worker.DoWork += delegate(object s, DoWorkEventArgs args)
        {
            args.Result = LongComputation();
        };

        worker.rep

        // RunWorkerCompleted will fire on the UI thread when the background process is complete
        worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
        {
            if (args.Error != null)
            {
                // an exception occurred on the background process, do some error handling
            }

            var result = args.Result;

            // do something on the UI with your result

            MyView.UpdateStatus("Ready");
        };

Наконец, вы можете использовать событие ReportProgress для обновления пользовательского интерфейса на логических этапах фонового процесса:

        worker.DoWork += delegate(object s, DoWorkEventArgs args)
        {
            FirstHalfComputation();

            // you can report a percentage back to the UI thread, or you can send 
            // an object payload back
            int completedPercentage = 50;
            object state = new SomeObject();
            worker.ReportProgress(completedPercentage , state); 

            SecondHalfComputation();
        };

        worker.WorkerReportsProgress = true;    // this is important, defaults to false
        worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
        {
            int completedPercentage = args.ProgressPercentage;
            SomeObject state = args.UserState as SomeObject

            // update a progress bar or do whatever makes sense
            progressBar1.Step = completedPercentage;
            progressBar1.PerformStep();
        };
person Jason    schedule 03.11.2013
comment
Могу ли я сделать MyWindowsForm newForm = new MyWindowsForm(args); newForm.Show() в потоке без пользовательского интерфейса? - person Unplug; 03.11.2013
comment
Я попробовал ваше предложение, newForm не появился, как я ожидал. Когда эти две линии не входят в поток BackgroundWorker, они отображаются вместе с линейной диаграммой. - person Unplug; 03.11.2013
comment
Создание новых форм должно происходить в RunWorkerCompleted. Он выполняется в потоке пользовательского интерфейса. Если вам нужны аргументы из LongComputation (для MyWindowsForm (args)), передайте их обратно в RunWorkerCompleted, используя свойство DoWorkerEventArgs Result. - person Jason; 03.11.2013
comment
Спасибо, Джейсон. Я применил то, что вы сказали, и заставил свое приложение работать. У меня есть дополнительный вопрос. Что делать, если я вызываю функцию в ссылке, у которой нет исходного кода, и функцию, которая требует много времени для создания графика? Я не могу использовать BackgroundWorker, потому что функция должна выполняться в потоке пользовательского интерфейса. - person Unplug; 04.11.2013
comment
Неважно. Я использую Dispatcher.Invoke для обновления статуса и устанавливаю приоритет Send, который является наивысшим приоритетом. Хотя пользователю все еще нужно подождать, по крайней мере, она знает, что происходит. - person Unplug; 04.11.2013

Я решил свою проблему с сохранением диспетчера из основного потока при загрузке формы и последующим вызовом диспетчера из переменной-члена, в которой я его сохранил, из потока BackgroundWorker:

Объявление переменной-члена в начале формы:

Dispatcher mDispatcherMain = null;

Сохранение диспетчера в Load-функции формы:

mDispatcherMain = Dispatcher.CurrentDispatcher;

Вызов основного потока из DoWork-функции BackgroundWorker:

mDispatcherMain.Invoke(new Action(() => { /* What you want to do */ }));
person Christian Larsson    schedule 02.10.2020