Greg D가 여기 에서 설명 하는 것과 유사한 SafeInvoke Control 확장 메서드가 있습니다 (IsHandleCreated 검사 제외).
나는 그것을 System.Windows.Forms.Form
다음과 같이 부르고있다 .
public void Show(string text) {
label.SafeInvoke(()=>label.Text = text);
this.Show();
this.Refresh();
}
때때로 (이 호출은 다양한 스레드에서 올 수 있음) 다음 오류가 발생합니다.
System.InvalidOperationException
발생
Message
= “창 핸들이 생성 될 때까지 컨트롤에서 Invoke 또는 BeginInvoke를 호출 할 수 없습니다.”
Source
= “System.Windows.Forms”
StackTrace: at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args) at System.Windows.Forms.Control.Invoke(Delegate method) at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16
무슨 일이 일어나고 어떻게 해결합니까? 가끔 한 번 작동하고 다음 번에 실패 할 수 있기 때문에 양식 작성의 문제가 아닌만큼 알고 있습니다. 문제는 무엇일까요?
추신. 나는 정말 WinForms에 정말 끔찍합니다. 전체 모델과 그 작업 방법을 설명하는 좋은 기사 시리즈를 아는 사람이 있습니까?
답변
잘못된 스레드에서 컨트롤을 만들고있을 수 있습니다. MSDN 의 다음 문서를 고려하십시오 .
즉, Invoke가 필요하지 않거나 (호출이 동일한 스레드에서 발생 함) 컨트롤이 다른 스레드에서 생성되었지만 컨트롤의 핸들이 아직 생성되지 않은 경우 InvokeRequired가 false를 반환 할 수 있습니다 .
컨트롤의 핸들이 아직 생성되지 않은 경우 컨트롤의 속성, 메서드 또는 이벤트를 단순히 호출하면 안됩니다. 이로 인해 컨트롤의 핸들이 백그라운드 스레드에 생성되어 메시지 펌프가없는 스레드에서 컨트롤이 격리되고 응용 프로그램이 불안정해질 수 있습니다.
InvokeRequired가 백그라운드 스레드에서 false를 반환 할 때 IsHandleCreated 값을 확인하여이 경우를 방지 할 수도 있습니다. 컨트롤 핸들이 아직 생성되지 않은 경우 Invoke 또는 BeginInvoke를 호출하기 전에 생성 될 때까지 기다려야합니다. 일반적으로 이는 양식이 표시되거나 Application.Run이 호출되기 전에 Application.Run (new MainForm ()에서와 같이) 애플리케이션에 대한 기본 양식의 생성자에서 백그라운드 스레드가 생성 된 경우에만 발생합니다.
이것이 당신에게 무엇을 의미하는지 봅시다. (당신의 SafeInvoke 구현을 본다면 추론하기가 더 쉬울 것입니다.)
구현이 IsHandleCreated 에 대한 검사를 제외하고 참조 된 구현과 동일하다고 가정 하고 논리를 따릅니다.
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
if (uiElement == null)
{
throw new ArgumentNullException("uiElement");
}
if (uiElement.InvokeRequired)
{
if (forceSynchronous)
{
uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
else
{
uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
}
else
{
if (uiElement.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
updater();
}
}
SafeInvoke
핸들이 생성되지 않은 컨트롤에 대해 GUI가 아닌 스레드에서 호출하는 경우를 고려하십시오 .
uiElement
null이 아니므로 uiElement.InvokeRequired
. MSDN 문서 (굵게 표시됨) InvokeRequired
는 false
다른 스레드에서 생성되었지만 핸들이 생성되지 않았기 때문에 반환됩니다 ! 이것은 우리가 백그라운드 스레드에서 제출 된 작업을 else
확인 IsDisposed
하거나 즉시 호출을 진행 하는 조건으로 우리 를 보냅니다 !
이 시점에서 모든 베팅은 해제됩니다. 두 번째 단락에서 언급했듯이 핸들이 메시지 펌프가없는 스레드에서 생성 되었기 때문입니다. 아마도 이것이 당신이 직면 한 경우입니까?
답변
InvokeRequired
신뢰할 수없는 것을 발견 했으므로 간단히
if (!this.IsHandleCreated)
{
this.CreateHandle();
}
답변
나는 생각 컨트롤이 아직 표시 /로드되어 있지 않은 경우 InvokeRequired 항상 false를 반환하기 때문에이 것을 (아직 완전히 확인). 현재 작동하는 것처럼 보이는 해결 방법을 수행했습니다. 이는 해당 작성자의 관련 컨트롤 핸들을 다음과 같이 간단하게 참조하는 것입니다.
var x = this.Handle;
( http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html 참조
)
답변
컨트롤을 만들지 않은 스레드에서 호출되는 경우 컨트롤의 핸들이 만들어 졌는지 확인하기 전에 Invoke
/ 호출에 연결하는 게시물의 메서드입니다 BeginInvoke
.
따라서 컨트롤을 만든 스레드가 아닌 스레드에서 메서드가 호출되면 예외가 발생합니다. 이는 원격 이벤트 또는 대기중인 작업 사용자 항목에서 발생할 수 있습니다.
편집하다
확인 InvokeRequired
하고 HandleCreated
호출하기 전에 호출하면 해당 예외가 발생하지 않아야합니다.
답변
를 사용하여 Control
표시하거나 다른 작업을 수행하기 전에 다른 스레드에서 를 사용하려는 Control
경우 생성자 내에서 핸들을 강제로 생성하는 것이 좋습니다. 이것은 CreateHandle
함수를 사용하여 수행됩니다.
“컨트롤러”논리가 WinForm에없는 다중 스레드 프로젝트에서이 함수는 Control
생성자에서이 오류를 방지하는 도구입니다 .
답변
메소드 호출을 호출하기 전에 다음을 추가하십시오.
while (!this.IsHandleCreated)
System.Threading.Thread.Sleep(100)
답변
다음과 같이 생성자에서 연결된 컨트롤의 핸들을 참조합니다.
참고 :이 솔루션에주의 하십시오 . 컨트롤에 핸들이있는 경우 크기 및 위치 설정과 같은 작업을 수행하는 것이 훨씬 느립니다. 이것은 InitializeComponent를 훨씬 느리게 만듭니다 . 더 나은 해결책은 컨트롤이 핸들을 갖기 전에 아무것도 배경으로하지 않는 것입니다.