По некоторым причинам этот безопасно выглядящий метод повышает классическое исключение.
Операция перекрестного потока, не допустимая: Управляйте 'statusLabel', к которому получают доступ от потока кроме потока, на котором он был создан.
Этот код, очевидно, должен звонить, анонимный метод через Вызывают, когда вызывают, требуется. Но исключение происходит время от времени.
У кого-либо была подобная проблема?
private void SetProgressBarValue(int progressPercentage)
{
Action setValue = () =>
{
var value = progressPercentage;
if (progressPercentage < 0)
value = 0;
else if (progressPercentage > 100)
value = 100;
statusProgressBar.Value = value;
statusLabel.Text = string.Format("{0}%", value);
};
if (InvokeRequired)
Invoke(setValue);
else
setValue();
}
Вот отслеживание стека:
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.System.Windows.Forms.Layout.IArrangedElement.SetBounds(Rectangle bounds, BoundsSpecified specified)
at System.Windows.Forms.Layout.DefaultLayout.xLayoutDockedControl(IArrangedElement element, Rectangle newElementBounds, Boolean measureOnly, ref Size preferredSize, ref Rectangle remainingBounds)
at System.Windows.Forms.Layout.DefaultLayout.LayoutDockedControls(IArrangedElement container, Boolean measureOnly)
at System.Windows.Forms.Layout.DefaultLayout.xLayout(IArrangedElement container, Boolean measureOnly, ref Size preferredSize)
at System.Windows.Forms.Layout.DefaultLayout.LayoutCore(IArrangedElement container, LayoutEventArgs args)
at System.Windows.Forms.Layout.LayoutEngine.Layout(Object container, LayoutEventArgs layoutEventArgs)
at System.Windows.Forms.Control.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.ScrollableControl.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Form.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.ToolStripItem.InvalidateItemLayout(String affectedProperty, Boolean invalidatePainting)
at System.Windows.Forms.ToolStripItem.OnTextChanged(EventArgs e)
at System.Windows.Forms.ToolStripItem.set_Text(String value)
at App.Image.Replace.ReplacementImageProcessForm.<>c__DisplayClass8.b__7() in ReplacementImageProcessForm.cs: line 114
at App.Image.Replace.ReplacementImageProcessForm.SetProgressBarValue(Int32 progressPercentage) in ReplacementImageProcessForm.cs: line 119
at App.Image.Replace.ReplacementImageProcessForm.replacer_BeginReplace(Object sender, EventArgs e) in ReplacementImageProcessForm.cs: line 76
at App.Image.Replace.DocumentReplacer.OnBeginReplace() in IDocumentReplacer.cs: line 72
at App.Image.Replace.DocumentReplacer.Replace(Int32 documentId, String replacementDocumentPath) in IDocumentReplacer.cs: line 108
Я все еще получаю ту же ошибку после реализации предложения John Saunders:
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.System.Windows.Forms.Layout.IArrangedElement.SetBounds(Rectangle bounds, BoundsSpecified specified)
at System.Windows.Forms.Layout.DefaultLayout.xLayoutDockedControl(IArrangedElement element, Rectangle newElementBounds, Boolean measureOnly, ref Size preferredSize, ref Rectangle remainingBounds)
at System.Windows.Forms.Layout.DefaultLayout.LayoutDockedControls(IArrangedElement container, Boolean measureOnly)
at System.Windows.Forms.Layout.DefaultLayout.xLayout(IArrangedElement container, Boolean measureOnly, ref Size preferredSize)
at System.Windows.Forms.Layout.DefaultLayout.LayoutCore(IArrangedElement container, LayoutEventArgs args)
at System.Windows.Forms.Layout.LayoutEngine.Layout(Object container, LayoutEventArgs layoutEventArgs)
at System.Windows.Forms.Control.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.ScrollableControl.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Form.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.ToolStripItem.InvalidateItemLayout(String affectedProperty, Boolean invalidatePainting)
at System.Windows.Forms.ToolStripItem.OnTextChanged(EventArgs e)
at System.Windows.Forms.ToolStripItem.set_Text(String value)
at App.Image.Replace.ReplacementImageProcessForm.<>c__DisplayClassa.<>c__DisplayClassc.b__9() in ReplacementImageProcessForm.cs: line 147
at App.Image.Replace.ReplacementImageProcessForm.InvokeIfNecessary(Control control, Action setValue) in ReplacementImageProcessForm.cs: line 156
at App.Image.Replace.ReplacementImageProcessForm.<>c__DisplayClassa.b__7() in ReplacementImageProcessForm.cs: line 145
at App.Image.Replace.ReplacementImageProcessForm.InvokeIfNecessary(Control control, Action setValue) in ReplacementImageProcessForm.cs: line 156
at App.Image.Replace.ReplacementImageProcessForm.SetProgressBarValue(Int32 progressPercentage) in ReplacementImageProcessForm.cs: line 132
at App.Image.Replace.ReplacementImageProcessForm.replacer_BeginReplace(Object sender, EventArgs e) in ReplacementImageProcessForm.cs: line 74
at App.Image.Replace.DocumentReplacer.OnBeginReplace() in IDocumentReplacer.cs: line 87
at App.Image.Replace.DocumentReplacer.Replace(Int32 documentId, String replacementDocumentPath) in IDocumentReplacer.cs: line 123
Попробуйте переопределить из 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 ().