Один путь состоит в том, чтобы заставить классы использовать помехи инициализаторы... Я не думаю, что они наследованы (это не будет работать, если они будут):
public class Dog extends Animal{
static
{
Animal a = new Dog();
//add a to the List
}
Это требует, чтобы Вы добавили этот код ко всем включенным классам. Но это старается не иметь большой ужасный цикл где-нибудь, тестируя каждый класс, ищущий детей Животного.
Попробуйте переопределить из Label, чтобы создать новый класс метки. Переопределите свойство текста и поместите на нем точку останова. Измените метку подозреваемого, чтобы вместо этого использовать ваш новый класс отладки. Я также обнаружил, что этот метод отлично подходит для выполнения базового профилирования и / или отладки в ваших формах, если вам нужно выяснить, где и как что-то обновляется.
public class MyLabel : Label
{
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
}
}
}
Используйте свой код и попытайтесь поймать heisenbug, вы иметь возможность прерывать каждый доступ к метке, поэтому, если он исходит из трассировки стека, чего вы не ожидаете и / или не из пути вашего кода вызова, у вас есть ошибка?
Это может иметь или не иметь непосредственное отношение к вашей ситуации, но может дать ключ к разгадке. Одна важная ненадежная абстракция, которую следует помнить о Windows Forms, заключается в том, что дескриптор окна Handle
не создается до тех пор, пока он не потребуется. Свойство Handle
создает настоящую Windows hwnd
только при первом вызове get
, чего не происходит, когда объект, производный от Control
. (как Windows Form) создается экземпляр. (Объект, производный от Control
, в конце концов, является просто классом .NET.) Другими словами, это свойство, которое лениво инициализируется.
Я уже обжигался этим раньше: Проблема в моем случае я правильно создал экземпляр формы в потоке пользовательского интерфейса, но я не использовал Show ()
до тех пор, пока данные не вернулись из вызова веб-службы, которая работала в рабочем потоке. Сценарий заключался в том, что никто не запрашивал у дескриптор формы
, до тех пор, пока к нему не обращались в рамках проверки InvokeRequired
, которая произошла, когда рабочий поток завершил свою работу. Итак, мой фоновый рабочий поток спросил форму: нужен ли мне InvokeRequired
? Реализация формы InvokeRequired
затем сказала: хорошо, позвольте мне взглянуть на мой дескриптор
, чтобы я мог увидеть, в каком потоке был создан мой внутренний hwnd
, а затем Я посмотрю, будете ли вы в той же теме. И тогда реализация Handle
сказала: ну, я еще не существую, так что позвольте мне прямо сейчас создать для себя hwnd
. (Вы видите, к чему это идет. Помните, мы все еще находимся в фоновом потоке, невинно обращаясь к свойству InvokeRequired
.)
Это привело к Handle
(и лежащему в его основе hwnd
) создается в потоке worker , который мне не принадлежит, и в котором не настроен насос сообщений для обработки сообщений Windows. Результат: мое приложение заблокировалось, когда другие вызовы были сделаны в ранее скрытое окно, поскольку эти вызовы были сделаны в основном потоке пользовательского интерфейса, который разумно предполагал, что все другие объекты, производные от Control
, также были созданы в эта ветка. В других случаях это могло вызвать странные исключения между потоками, потому что InvokeRequired
неожиданно возвращал false, поскольку дескриптор
был создан в потоке, который отличается от потока, в котором была создана форма.
Но только иногда. У меня была функция, с помощью которой пользователь мог вызвать саму форму Show ()
через меню, а затем она отключила бы себя, пока заполнялась данными в фоновом режиме (показывая анимацию пульсации). Если бы они сделали это первыми, то все было бы в порядке: дескриптор
был создан в потоке пользовательского интерфейса (в обработчике событий пункта меню), и поэтому InvokeRequired
вел себя должным образом, когда рабочий поток завершил получение данных из веб-службы. Но если мой фоновый поток, который периодически запускался (это был планировщик событий, похожий на диалоговое окно напоминания о событии в Outlook), обращался к веб-службе и пытался открыть форму,
Вы видите эту ошибку при запуске приложения из отладчика или при автономном запуске?
У меня .NET Framework некорректно вызывал это исключение при работе в отладчике. Есть что-то особенное в отладчике, из-за которого флаг InvokeRequired элемента управления неправильно возвращает значение true, даже если код выполняется внутри основного потока пользовательского интерфейса. Это очень важно для меня, и это всегда происходит после того, как наш контроль был удален. Наша трассировка стека выглядит так:
System.InvalidOperationException: Cross-thread operation not valid: Control 'cboMyDropDown' accessed from a thread other than the thread it was created on. at System.Windows.Forms.Control.get_Handle() at System.Windows.Forms.TextBox.ResetAutoComplete(Boolean force) at System.Windows.Forms.TextBox.Dispose(Boolean disposing) at OurApp.Controls.OurDropDownControl.Dispose(Boolean disposing) at System.ComponentModel.Component.Dispose()
Вы можете увидеть источник ошибки в исходном коде .NET Framework:
public class Control //...
{ //...
public IntPtr Handle
{
get
{
if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired)
{
throw new InvalidOperationException(System.Windows.Forms.SR.GetString("IllegalCrossThreadCall", new object[] { this.Name }));
}
if (!this.IsHandleCreated)
{
this.CreateHandle();
}
return this.HandleInternal;
}
}
}
При запуске в отладчике checkForIllegalCrossThreadCalls
истинно, inCrossThreadSafeCall
ложно, а this.InvokeRequired
истинно, несмотря на то, что он находится в потоке пользовательского интерфейса!
Обратите внимание, что Control.InvokeRequired
делает следующее:
int windowThreadProcessId = System.Windows.Forms.SafeNativeMethods.GetWindowThreadProcessId(ref2, out num);
int currentThreadId = System.Windows.Forms.SafeNativeMethods.GetCurrentThreadId();
return (windowThreadProcessId != currentThreadId);
Также обратите внимание, что наше приложение использует .NET Framework 2.0. Не уверен, что это проблема в будущих версиях, но я подумал, что все равно напишу этот ответ для потомков.
Подводя итог, у вас есть метод частного экземпляра SetProgressBarValue
. Это метод экземпляра элемента управления или формы. Этот элемент управления или форма содержит другие элементы управления, statusProgressBar
и statusLabel
. Итак, по сути, вы делаете следующее:
if (this.InvokeRequired)
{
Invoke(
(Action) delegate
{
statusProgressBar.Value = 0; // TOUCH
statusLabel.Text = string.Format("{0}%", 0); // TOUCH
});
}
else
{
statusProgressBar.Value = 0; // TOUCH
statusLabel.Text = string.Format("{0}%", 0); // TOUCH
}
Этот код предполагает, что если this.InvokeRequired == false, то это подразумевает statusProgressBar.InvokeRequired == false и statusLabel. InvokeRequired == false. Я предполагаю, что вы обнаружили ситуацию, когда это не так.
Попробуйте изменить код на:
private void SetProgressBarValue(int progressPercentage)
{
InvokeIfNecessary(
this, () =>
{
var value = progressPercentage;
if (progressPercentage < 0)
{
value = 0;
}
else if (progressPercentage > 100)
{
value = 100;
}
InvokeIfNecessary(
statusProgressBar.GetCurrentParent(),
() => statusProgressBar.Value = value);
InvokeIfNecessary(
statusLabel.GetCurrentParent(),
() =>
statusLabel.Text = string.Format("{0}%", value));
});
}
private static void InvokeIfNecessary(Control control, Action setValue)
{
if (control.InvokeRequired)
{
control.Invoke(setValue);
}
else
{
setValue();
}
}
Я подозреваю, что вы могли каким-то образом вызвать дескрипторы окон этих трех элементов управления, которые были созданы в разных потоках. Я думаю, что этот код будет работать, даже если все три дескриптора окна были созданы в разных потоках.
Попробуйте использовать BeginInvoke () вместо Invoke ().
Ответ Николаса Пьясеки пролил для меня много света на эту проблему. У меня часто возникала эта странная ошибка, и я благодарен за информацию о причинах ее возникновения (Handle для элемента управления, вероятно, лениво загружается при первом вызове this.InvokeRequired из фонового потока)
Я создаю много пользовательского интерфейса динамически (в потоке UI) и связываю с презентерами (паттерн MVP), которые часто запускают рабочие потоки до первого показа пользовательского интерфейса. Разумеется, UI обновляется, и эти обновления передаются в поток UI с помощью this.InvokeRequired/BeginInvoke, однако в этот момент я предполагаю, что хэндл может быть создан в рабочем потоке.
Для меня нарушение межпотокового взаимодействия происходит в методе MainForm dispose, когда пользователь закрывает приложение. В качестве обходного пути я рекурсивно перебираю дочерние элементы управления, избавляясь от них и их дочерних элементов при закрытии главной формы. Затем я сократил список элементов управления, которые я утилизировал, и в конце концов свел его к одному элементу управления, который вызывал нарушение доступа. К сожалению, я не смог решить проблему напрямую (вызов CreateControl() или CreateHandle() для данного элемента управления не привел к решению проблемы), но я смог обойти проблему, оставив рекурсивное удаление на месте при закрытии приложения.
Почему это работает, а встроенный метод Form.Dispose() - нет, я не знаю.
В любом случае, в будущем я буду более осторожен при создании элементов управления рядом с рабочими потоками, теперь я знаю, что Handles лениво загружаются, так что спасибо!