BackgroundWorker: InvalidOperationException в RunWorkerCompleted

У меня есть WinForm с backgroundWorker:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SeoTools.Utils;

namespace SeoTools.UI
{
    public partial class UIProgress : Form
    {
        public UIProgress(DoWorkEventHandler doWorkEventHandler, RunWorkerCompletedEventHandler runWorkerCompletedEventHandler)
        {
            InitializeComponent();
            this.backgroundWorker.WorkerReportsProgress = true;
            this.backgroundWorker.WorkerSupportsCancellation = true;
            this.backgroundWorker.DoWork += doWorkEventHandler;
            this.backgroundWorker.RunWorkerCompleted += runWorkerCompletedEventHandler;
        }

        public void Start()
        {
            var foo = SynchronizationContext.Current;
            backgroundWorker.RunWorkerAsync();
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            btnStop.Enabled = false;
            btnStop.Text = "Stopping...";
            backgroundWorker.CancelAsync(); 
        }

       private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        try
        {
            wdgProgressBar.Value = e.ProgressPercentage;
            if (this.Visible == false)
            {
                this.ShowDialog();
                this.Update();
            }
        }
        catch (InvalidOperationException) {} 
    }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.Hide(); //Here I get a InvalidOperationException
            this.Dispose();
        }    
    }
}

Первый раз запускаю, работает нормально. Но во второй раз я получаю InvalidOperationException при вызове this.Hide().

«Дополнительная информация: недопустимая межпоточная операция: доступ к элементу управления UIProgress из потока, отличного от потока, в котором он был создан».

Странно то, что при первом запуске foo в Start() является WindowsFormsSyncronizationContext, но при второй попытке это System.Threading.SyncronizationContext.

Приложение, которое я пишу, представляет собой плагин ExcelDna.

РЕДАКТИРОВАТЬ

Start() вызывается так:

 UIProgress uiProgress = new UIProgress(
                delegate(object sender, DoWorkEventArgs args)
                {
                   ....
                },
                delegate(object sender, RunWorkerCompletedEventArgs args)
                    {
                       ...
                    }
            );
            uiProgress.Start();

person Niels Bosma    schedule 07.12.2014    source источник
comment
Как вызывается Start?   -  person kennyzx    schedule 07.12.2014
comment
@kennyzx я обновил свой вопрос   -  person Niels Bosma    schedule 07.12.2014
comment
Я нашел старую публикацию в SyncronizationContext. Техника заключается в том, что вы можете сохранить WindowsFormsSyncronizationContext для последующего использования. Не знаю, как он переключается на другой SyncronizationContext, хотя, может быть, это среда Excel-DNA ... требует некоторого времени на отладку.   -  person kennyzx    schedule 08.12.2014
comment
То же, что и kennyzx.. вот еще один пост — блоги. msdn.com/b/kaelr/archive/2007/09/05/ Я думаю, это как-то связано с тем, как работает Excel-DNA (никогда не использовал и даже не слышал об этом раньше... но выглядит круто)   -  person Vikas Gupta    schedule 10.12.2014
comment
Лучший вариант — использовать begininvoke в обратном вызове.   -  person Rafa    schedule 16.12.2014


Ответы (6)


Ваш метод Start() должен вызываться из кода, который выполняется в потоке пользовательского интерфейса, чтобы фоновый рабочий процесс работал правильно. Это было не тогда, когда вы получаете это исключение. Добавьте защитный код в свой метод, чтобы вы могли диагностировать этот сбой:

    public void Start()
    {
        if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA) {
            throw new InvalidOperationException("Bug! Code called from a worker thread");
        }
        backgroundWorker.RunWorkerAsync();
    }

Теперь вы можете установить точку останова на операторе throw и использовать окно стека вызовов отладчика, чтобы выяснить, почему это произошло.

person Hans Passant    schedule 07.12.2014
comment
InvokeRequired для меня всегда ложно. - person Niels Bosma; 07.12.2014
comment
Это означает, что объект UIProgress создан не в том потоке. Код обновлен. Он более универсален, как опубликовано, но вы получите лучшую трассировку стека, когда поместите тест в свой конструктор. - person Hans Passant; 12.12.2014

Вы вызываете операцию пользовательского интерфейса в фоновом потоке. Это причина этого исключения. Я бы использовал совершенно другой метод, чтобы сделать форму прогресса лучше всего — использовать Task с IProgress. Другой способ использовать это:

private void backgroundWorker_ProgressChanged( object sender , ProgressChangedEventArgs e )
    {

      this.UpdateOnMainThread(
        ( ) =>
        {
          wdgProgressBar.Value = e.ProgressPercentage;
          if ( this.Visible == false )
          {
            this.ShowDialog( );
            this.Update( );
          }
        } );
    }

    private void UpdateOnMainThread( Action action )
    {
      if ( this.InvokeRequired )
      {
        this.BeginInvoke( ( MethodInvoker ) action.Invoke);
      }
      else
      {
        action.Invoke( );
      }
    }

    private void backgroundWorker_RunWorkerCompleted( object sender , RunWorkerCompletedEventArgs e )
    {
      this.UpdateOnMainThread(
        ( ) =>
        {
          this.Hide( ); //Here I get a InvalidOperationException
          this.Dispose( );
        } );

    }
person Radin Gospodinov    schedule 15.12.2014

Используйте метод BeginInvoke() в форме:

//http://msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.110).aspx

    private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.BeginInvoke(new InvokeDelegate(InvokeMethod));               
    }

    public delegate void InvokeDelegate();

    public void InvokeMethod()
    {
        this.Hide(); 
        this.Dispose();
    }
person opadro    schedule 11.12.2014

Я думаю, вы можете найти некоторую помощь здесь: BackgroundWorker скрывает окно формы после завершения . Однако не забудьте отсоединить события BackgroundWorker и остановить BackgroundWorker, как описано здесь: Правильный способ удаления BackGroundWorker . Проблема может быть в

this.Dispose();

в событии backgroundWorker_RunWorkerCompleted. При этом вы избавляетесь от страницы формы. Это то, что вы хотите сделать? Или вы хотите избавиться от BackgroundWorker? При удалении страницы формы освобождаются все ресурсы, поэтому выполнение this.Hide(); во второй раз может быть ошибкой.

Для получения дополнительной информации вы можете увидеть следующие ссылки: C# Form.Close vs Form.Dispose и метод Form.Dispose< /а>

person Ninita    schedule 16.12.2014

Вы должны проверить эту ссылку Как обновить графический интерфейс с другого поток в C#?

Вероятно, есть все возможные ответы

Надеюсь на эту помощь

person Miguel    schedule 16.12.2014

Вы выполняете вызов основного потока из потока, который не может манипулировать пользовательским интерфейсом. Самый простой способ — использовать анонимный вызов делегата.

Измените это:

        if (this.Visible == false)
        {
            this.ShowDialog();
            this.Update();
        }

Для этого:

this.Invoke((MethodInvoker) delegate { 
        if (this.Visible == false)
        {
            this.ShowDialog();
            this.Update();
        }    
});

Это не самый оптимизированный способ, но он работает невероятно быстро без особого перекодирования. :)

person Madison Tobal    schedule 16.12.2014