Вопрос

У меня есть простой UserControl для подкачки базы данных, который использует контроллер для выполнения реальных вызовов DAL.Я использую BackgroundWorker выполнять тяжелую работу и на OnWorkCompleted если я снова включу некоторые кнопки, изменю TextBox.Text свойство и вызвать событие для родительской формы.

Форма A содержит мой UserControl.Когда я нажимаю на какую-нибудь кнопку, открывающую форму Б, даже если я ничего «там» не делаю, а просто закрываю ее и пытаюсь загрузить следующую страницу из своей базы данных, OnWorkCompleted вызывается в рабочем потоке (а не в моем основном потоке) и выдает межпотоковое исключение.

На данный момент я добавил проверку на InvokeRequired в обработчике, но это не весь смысл OnWorkCompleted должен быть вызван в главном потоке?Почему это не сработает так, как ожидалось?

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

Мне удалось сузить проблему до ArcGIS и BackgroundWorker.У меня есть следующее решение, которое добавляет команду в arcmap, которая открывает простой Form1 с двумя кнопками.

Первая кнопка запускает BackgroundWorker который спит 500 мс и обновляет счетчик.в RunWorkerCompleted метод, который он проверяет InvokeRequired, и обновляет заголовок, чтобы показать, был ли метод изначально запущен внутри основного потока или рабочего потока.Вторая кнопка просто открывается Form2, который ничего не содержит.

Сначала все вызовы RunWorkerCompletedare создаются внутри основного потока (как и ожидалось — в этом весь смысл метода RunWorkerComplete. По крайней мере, насколько я понимаю из MSDN на BackgroundWorker)

После открытия и закрытия Form2, RunWorkerCompleted всегда вызывается в рабочем потоке.Хочу добавить, что могу просто оставить это решение проблемы как есть (проверьте наличие InvokeRequired в RunWorkerCompleted метод), но я хочу понять, почему это происходит вопреки моим ожиданиям.В моем «настоящем» коде я хотел бы всегда знать, что RunWorkerCompleted метод вызывается в основном потоке.

Мне удалось указать на проблему в form.Show(); команда в моем BackgroundTesterBtn - если я использую ShowDialog() вместо этого у меня нет проблем (RunWorkerCompleted всегда выполняется в основном потоке).мне нужно использовать Show() в моем проекте ArcMap, чтобы пользователь не был привязан к форме.

Я также попытался воспроизвести ошибку в обычном проекте WinForms.Я добавил простой проект, который просто открывает первую форму без ArcMap, но в этом случае мне не удалось воспроизвести ошибку - RunWorkerCompleted работал в основном потоке, использовал ли я Show() или ShowDialog(), до и после открытия Form2.Я попытался добавить третью форму в качестве основной перед моим Form1, но это не изменило результата.

Здесь это мой простой sln (VS2005sp1) - он требует

ESRI.ArcGIS.ADF(9.2.4.1420)

ESRI.ArcGIS.ArcMapUI(9.2.3.1380)

ESRI.ArcGIS.SystemUI (9.2.3.1380)

Это было полезно?

Решение

Это похоже на ошибку:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=116930

http://thedatafarm.com/devlifeblog/archive/2005/12/21/39532.aspx

Поэтому я предлагаю использовать пуленепробиваемый (псевдокод):

if(control.InvokeRequired)
  control.Invoke(Action);
else
  Action()

Другие советы

Разве не вся суть OnWorkCompleted должен быть вызван в главном потоке?Почему это не сработает так, как ожидалось?

Нет, это не так.
Вы не можете просто запустить что-то старое в каком-то старом потоке.Потоки — это не вежливые объекты, которым можно просто сказать «запустите это, пожалуйста».

Лучшей мысленной моделью потока является грузовой поезд.Как только это произойдет, оно пойдет своим путем.Вы не можете изменить его курс или остановить его.Если вы хотите повлиять на него, вам придется либо подождать, пока он доберется до следующей железнодорожной станции (например:пусть он вручную проверит некоторые события) или пустит его под откос (Thread.Abort и исключения CrossThread имеют почти те же последствия, что и сход поезда с рельсов...остерегаться!).

Элементы управления Winforms вроде поддерживают такое поведение (у них есть Control.BeginInvoke который позволяет запускать любую функцию в потоке пользовательского интерфейса), но это работает только потому, что у них есть специальный перехватчик сообщений пользовательского интерфейса Windows и написаны некоторые специальные обработчики.Если использовать приведенную выше аналогию, их поезд проверяется на станции и периодически ищет новые направления, и вы можете использовать эту возможность, чтобы публиковать свои собственные маршруты.

А BackgroundWorker предназначен для общего назначения (его нельзя привязать к графическому интерфейсу Windows), поэтому он не может использовать Windows Control.BeginInvoke функции.Он должен предполагать, что ваш основной поток — это неостановимый «поезд», делающий свое дело, поэтому завершенное событие должно выполняться в рабочем потоке или не выполняться вообще.

Однако, поскольку вы используете winforms, в вашем OnWorkCompleted обработчик, вы можете заставить окно выполнить другой обратный вызов с помощью BeginInvoke функционал, о котором я говорил выше.Так:

// Assume we're running in a windows forms button click so we have access to the 
// form object in the "this" variable.
void OnButton_Click(object sender, EventArgs e )
    var b = new BackgroundWorker();
    b.DoWork += ... blah blah

    // attach an anonymous function to the completed event.
    // when this function fires in the worker thread, it will ask the form (this)
    // to execute the WorkCompleteCallback on the UI thread.
    // when the form has some spare time, it will run your function, and 
    // you can do all the stuff that you want
    b.RunWorkerCompleted += (s, e) { this.BeginInvoke(WorkCompleteCallback); }
    b.RunWorkerAsync(); // GO!
}

void WorkCompleteCallback()
{
    Button.Enabled = false;
    //other stuff that only works in the UI thread
}

Также не забывайте следующее:

Обработчик событий RunWorkerCompleted всегда должен проверять свойства Error и Canceled перед доступом к свойству Result.Если возникло исключение или операция была отменена, доступ к свойству Result вызывает исключение.

А BackgroundWorker проверяет, указывает ли экземпляр делегата на класс, который поддерживает интерфейс ISynchronizeInvoke.Ваш уровень DAL, вероятно, не реализует этот интерфейс.Обычно вы используете BackgroundWorker на Form, который поддерживает этот интерфейс.

В случае, если вы хотите использовать BackgroundWorker из уровня DAL и хотите обновить пользовательский интерфейс оттуда, у вас есть три варианта:

  • ты бы продолжал звонить Invoke метод
  • реализовать интерфейс ISynchronizeInvoke в классе DAL и перенаправить вызовы вручную (всего три метода и свойство)
  • прежде чем вызывать BackgroundWorker (итак, в потоке пользовательского интерфейса), чтобы вызвать SynchronizationContext.Current и сохранить экземпляр содержимого в переменной экземпляра.А SynchronizationContext затем даст вам Send метод, который будет делать именно то, что Invoke делает.

Лучший способ избежать проблем с перекрестной многопоточностью в графическом интерфейсе — это используйте SynchronizationContext.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top