Обновление GUI с использованием BackgroundWorker - PullRequest
5 голосов
/ 04 марта 2011

Я искал и обнаружил, что хорошим способом выполнения фоновой работы и обновления графического интерфейса пользователя является использование фоновых рабочих.Однако, выполняя эту (глупую) небольшую задачу (считая от 1 до 10000), он не обновляет содержимое метки, а печатает для отладки!(Конечно, это просто решение для другого проекта ...)

Вот код:

public partial class MainWindow : Window
{
    BackgroundWorker bw = new BackgroundWorker();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {

        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
        bw.WorkerReportsProgress = true;
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
        bw.RunWorkerAsync();

    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        MessageBox.Show("DONE");

    }

    void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        label1.Content = "going here: "+e.ProgressPercentage;
        Debug.WriteLine(e.ProgressPercentage);
    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i=0; i < 10000; i++)
        {
            bw.ReportProgress((i*100)/10000);
        }
    }

}

Ответы [ 3 ]

12 голосов
/ 04 марта 2011

Событие ProgressChanged возникает в потоке пользовательского интерфейса, а не в рабочем потоке. В вашем коде рабочий поток почти ничего не делает (просто цикл от 0 до 10000 и вызов ReportProgress), большая часть работы выполняется в потоке пользовательского интерфейса. По сути, вы отправляете слишком много уведомлений о прогрессе. Из-за этого поток пользовательского интерфейса почти всегда занят и не имеет времени для визуализации нового содержимого метки.

Рендеринг в WPF выполняется не сразу при изменении свойства элемента управления, он выполняется в отдельном кадре диспетчера, который обрабатывается, когда диспетчеру не нужно делать что-либо более срочное в зависимости от приоритета задачи. Приоритет, используемый для рендеринга, имеет значение 7 (DispatcherPriority.Render); событие ProgressChanged передается в поток пользовательского интерфейса с приоритетом 9 (DispatcherPriority.Normal), , как указано в MSDN . Таким образом, уведомления ProgressChanged всегда имеют более высокий приоритет, чем рендеринг, и, поскольку они продолжают поступать, диспетчер никогда не успевает обработать задачи рендеринга.

Если вы просто уменьшите частоту уведомлений, ваше приложение должно работать нормально (в настоящее время вы отправляете 100 уведомлений для каждого значения в процентах, что бесполезно):

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i = 0; i < 10000; i++)
        {
            if (i % 100 == 0)
                bw.ReportProgress(i / 100);
        }
    }
3 голосов
/ 04 марта 2011
this.Dispatcher.BeginInvoke( (Action) delegate(){
   label1.Content = "going here: "+e.ProgressPercentage;
});
0 голосов
/ 04 марта 2011

Попробуйте изменить метку, используя что-то вроде этого:

string Text = "going here: " + e.ProgressPercentage;
this.Invoke((MethodInvoker)delegate {
    label1.Content = newText;
});

Обратите внимание, что я не уверен, что это будет работать. Я не могу проверить это сейчас. Если это не сработает, дайте мне знать, и я удалю ответ.

Если вам нужен канонический способ сделать именно то, что вы хотите, посмотрите ответ Hath в этом посте: Как обновить графический интерфейс из другого потока?

...