Разница между BackgroundWorker.ReportProgress() и Control.BeginInvoke()

В чем разница между вариантами 1 и 2 в следующем?

    private void BGW_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i=1; i<=100; i++)
        {
            string txt = i.ToString();
            if (Test_Check.Checked)
                //OPTION 1
                Test_BackgroundWorker.ReportProgress(i, txt); 
            else
                //OPTION 2
                this.BeginInvoke((Action<int, string>)UpdateGUI, 
                                  new object[] {i, txt});
        }
    }

    private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        UpdateGUI(e.ProgressPercentage, (string)e.UserState);
    }

    private void UpdateGUI(int percent, string txt)
    {
        Test_ProgressBar.Value = percent;
        Test_RichTextBox.AppendText(txt + Environment.NewLine);
    }

Глядя на рефлектор, Control.BeginInvoke(), похоже, использует:

this.FindMarshalingControl().MarshaledInvoke(this, method, args, 1);

Который, кажется, в конечном итоге вызывает некоторые нативные функции, такие как PostMessage(), не может точно понять поток от рефлектора (надоедливая оптимизация перехода компилятора)

Принимая во внимание, что BackgroundWorker.Invoke() использует:

this.asyncOperation.Post(this.progressReporter, args);

Что, кажется, в конечном итоге вызывает ThreadPool.QueueUserWorkItem()

(Я просто предполагаю, что это соответствующие вызовы функций для каждого случая.) Если я правильно понимаю, использование ThreadPool не гарантирует порядок выполнения, тогда как использование механизма Post гарантирует. Может быть, это и есть разность потенциалов? (EDIT - я не мог синтезировать такую ​​ситуацию - порядок вызовов, кажется, сохраняется в обоих случаях, по крайней мере, в моих простых тестах.)

Спасибо!


person Ohad Schneider    schedule 19.05.2010    source источник


Ответы (3)


Они оба одинаковы. Вызов, который вы видите в BackgroundWorker, использует SynchronizationContext. Действительно, реализация метода Post() по умолчанию использует пул потоков, но при запуске приложения Windows Forms контекст синхронизации по умолчанию заменяется на WindowsFormsSynchronizationContext, который фактически вызывает Control.BeginInvoke().

person Eli Arbel    schedule 20.05.2010

Одно большое отличие состоит в том, что Control.Invoke будет блокироваться до тех пор, пока не будет выполнен и завершен вызов UpdateGUI, тогда как BackgroundWorker.ReportProgress не будет не блокироваться (он вернется немедленно, до события BackgroundWorker). .

Если вы хотите, чтобы они вели себя одинаково, вместо этого вызовите Control.BeginInvoke (который не блокируется).

person MusiGenesis    schedule 19.05.2010
comment
Спасибо, но это не входило в мои намерения - я просто пересмотрел вопрос - person Ohad Schneider; 19.05.2010
comment
Мой ответ такой же. ReportProgress — асинхронный, Control.Invoke — синхронный. В случае индикатора выполнения выбор одного или другого, вероятно, не будет иметь заметного эффекта. Если вы используете ReportProgress, обновление графического интерфейса в любом случае будет почти мгновенным - единственная причина, по которой этого не будет, - это если ваша форма занята обработкой множества других событий одновременно. - person MusiGenesis; 19.05.2010
comment
Да, но теперь я спрашиваю о разнице между ReportProgress и BeginInvoke - person Ohad Schneider; 19.05.2010
comment
Разница была бы гораздо более существенной, если бы ваш метод UpdateGUI выполнял кучу трудоемкой работы. Тогда у вас будет выбор между запуском фонового процесса на максимальной скорости, но с отложенными обновлениями графического интерфейса (ReportProgress) или получением обновлений графического интерфейса в реальном времени, но с замедлением фонового процесса (Control.Invoke). - person MusiGenesis; 19.05.2010
comment
Ой, извините, я не заметил изменения BeginInvoke. Забудьте о том, что я сказал в комментариях — вы, кажется, уже знаете об этом. :) - person MusiGenesis; 19.05.2010
comment
Что показывает Reflector, когда вы переходите на BeginInvoke? Я не удивлюсь, если это приведет к тому же коду, что и ReportProgress. - person MusiGenesis; 19.05.2010
comment
Я включил свои (ограниченные) выводы отражателя ниже кода :) - person Ohad Schneider; 19.05.2010

Я нашел существенную разницу. Закрытие формы во время работы BGW приведет к тому, что this.Invoke() и this.BeginInvoke() вызовут исключение ObjectDisposedException. Механизм BGW ReportProgress обходит это. Чтобы насладиться лучшим из обоих миров, хорошо работает следующий шаблон.

public partial class MyForm : Form
{
    private void InvokeViaBgw(Action action)
    {
        Packing_Worker.ReportProgress(0, action);
    }

    private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (this.IsDisposed) return; //You are on the UI thread now, so no race condition

        var action = (Action)e.UserState;
        action();
    }

    private private void BGW_DoWork(object sender, DoWorkEventArgs e)
    {
       //Sample usage:
       this.InvokeViaBgw(() => MyTextBox.Text = "Foo");
    }
}
person Ohad Schneider    schedule 04.07.2010