Invoke 또는 BeginInvoke는 창 핸들이 만들어 질 때까지 컨트롤에서 호출 할 수 없습니다.


82

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에 정말 끔찍합니다. 전체 모델과 그 작업 방법을 설명하는 좋은 기사 시리즈를 아는 사람이 있습니까?


1
링크에서 이상한 일이 벌어지고 있습니다 ... 마크 업과 미리보기가 정확합니다 ... 이상합니다.
George Mauer

Show는 어떤 컨텍스트에서 호출됩니까? 예를 들어 Form의 생성자에서 호출 된 적이 있습니까? HandleCreated 이벤트에 의해 트리거 된 메시지에 대해 표시 할 호출에 대한 메시지를 로깅하여 이미 핸들이 생성 된 개체에서만 show를 호출하고 있는지 확인하는 것이 유용 할 수 있습니다.
Greg D

응용 프로그램은 무엇이며 어떻게 설계됩니까? this.Show ()는 무엇을합니까? (나는 이것이 이것보다 더 많은 일을한다고 가정하고있다 .Visible = true;) webforms에 대한 참조가 오타입니까?
Greg D

this.Show ()는 기본 Form.Show ()이므로 무엇이든 할 수 있습니다. 생성자에서 대화 상자를 가져 오지 않습니다. 간단한 Notify (string) 메서드를 가진 INotifier 서비스 구현에 의해 호출됩니다
George Mauer

4
다시 살펴보면 1 년이 지난 후 IsHandleCreated확인이 존재 하는 이유 때문에 오류가 발생한 것 같습니다 . 아직 생성되지 않은 컨트롤의 (Send a message to) 속성을 변경하려고합니다. 이 상황에서 할 수있는 한 가지는 컨트롤이 생성되기 전에 제출 된 델리게이트를 대기열에 추가 한 다음 HandleCreated이벤트 에서 실행하는 것입니다 .
Greg D

답변:


77

잘못된 스레드에서 컨트롤을 만들고있을 수 있습니다. 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가 아닌 스레드에서 호출하는 경우를 고려하십시오 .

uiElementnull이 아니므로 uiElement.InvokeRequired. MSDN 문서 (굵게 표시됨) InvokeRequiredfalse다른 스레드에서 생성되었지만 핸들이 생성되지 않았기 때문에 반환됩니다 ! 이것은 우리가 백그라운드 스레드에서 제출 된 작업을 else확인 IsDisposed하거나 즉시 호출을 진행 하는 조건으로 우리 를 보냅니다 !

이 시점에서 모든 베팅은 해제됩니다. 두 번째 단락에서 언급했듯이 핸들이 메시지 펌프가없는 스레드에서 생성 되었기 때문입니다. 아마도 이것이 당신이 직면 한 경우입니까?


EndInvoke뒤에 를 포함해야합니까 BeginInvoke?
Odys

@odyodyodys : 짧은 대답 : 아니요. 이것은 당신이 필요로하지 않는 마법적이고 특별한 경우입니다. 더 긴 답변 :이 답변에 대한 의견 읽기 : stackoverflow.com/a/714680/6932
Greg D

1
이 답변과 MSDN 기사는 Handle이 생성되지 않았기 때문에 false를 반환하는 InvokeRequired에 관한 것입니다. 그러나 InvokeRequired가 true를 반환 한 후 Beginvoke / Invoke가 호출되면 OP가 예외를받습니다. 핸들이 아직 생성되지 않은 경우 InvokeRequired가 어떻게 true를 반환 할 수 있습니까?
thewpfguy

wrt IsDisposed에 대한 경쟁 조건도 있습니다. IsDisposed는 테스트시 false 일 수 있지만 제출 된 작업이 완전히 실행되기 전에 true가됩니다. 두 가지 선택 사항은 (a) InvalidOperationException을 무시하고 (b) 잠금을 사용하여 제출 된 작업 및 컨트롤 처리 메서드에서 중요한 섹션을 만드는 것입니다. 첫 번째는 해킹처럼 느껴지고 두 번째는 고통입니다.
blearyeye

37

InvokeRequired신뢰할 수없는 것을 발견 했으므로 간단히

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}

5
이로 인해 다른 스레드에서 두 개의 핸들이 생성되지 않을 수 있습니까? 핸들은, 당신은 단지 .. 이벤트의 타이밍 / 위해 개선해야 생성을하셔야합니다
데니스 스키드 모어

Nice-나는 이것을 접근하는 것보다 이것을 선호합니다. (a) 사용하지 않는 변수가없고 (b) 무슨 일이 일어나고 있는지 분명하기 때문에 처리하십시오
Dunc

5
MSDN : "컨트롤의 핸들이 아직 생성되지 않은 경우 컨트롤의 속성, 메서드 또는 이벤트를 단순히 호출해서는 안됩니다. 이로 인해 컨트롤의 핸들이 백그라운드 스레드에 생성되어 컨트롤이 메시지 펌프가없는 스레드로 인해 응용 프로그램이 불안정 해집니다. " 연습의 요점은 잘못된 스레드에 핸들을 만들지 않는 것입니다. 이 호출이 GUI 스레드가 아닌 스레드에서 발생하면 뱅-당신은 죽었습니다.
Greg D

25

비슷한 질문에 대한 대답 은 다음과 같습니다 .

나는 생각 컨트롤이 아직 표시 /로드되어 있지 않은 경우 InvokeRequired 항상 false를 반환하기 때문에이 것을 (아직 완전히 확인). 현재 작동하는 것처럼 보이는 해결 방법을 수행했습니다. 이는 해당 작성자의 관련 컨트롤 핸들을 다음과 같이 간단하게 참조하는 것입니다.

var x = this.Handle; 

( http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html 참조 )


매우 흥미로운 기사 btw. 감사.
Yann Trevin 2010

감사합니다. 백그라운드 스레드에서 안팎으로 애니메이션 처리해야하는 숨겨진 양식이 있었기 때문에이 작업이 저에게 효과적이었습니다. 그것은 나를 위해 일하고있어 문제는 핸들을했다 참조
존 맥

이것은 "기능"보다는 오류가 적지 만 최신 버전의 .net에서 여전히 문제입니다. 객체에 "시계"를 놓고 속성을 탐색하는 것도 핸들을 보는 것과 동일하다는 점에 주목할 가치가 있습니다. 당신이 그것을 볼 때 그것이 작동하는 곳에서 양자 디버깅 말도 안되는 소리로 끝납니다.
Tony Cheetham

5

컨트롤을 만들지 않은 스레드에서 호출되는 경우 컨트롤의 핸들이 만들어 졌는지 확인하기 전에 Invoke/ 호출에 연결하는 게시물의 메서드입니다 BeginInvoke.

따라서 컨트롤을 만든 스레드가 아닌 스레드에서 메서드가 호출되면 예외가 발생합니다. 이는 원격 이벤트 또는 대기중인 작업 사용자 항목에서 발생할 수 있습니다.

편집하다

확인 InvokeRequired하고 HandleCreated호출하기 전에 호출하면 해당 예외가 발생하지 않아야합니다.


내가 올바르게 이해한다면, 호출하는 스레드가 컨트롤이 생성 된 스레드와 다를 때마다 이런 일이 발생한다고 말하는 것입니다. 이벤트가 호출 될 스레드를 보장 할 수 없습니다. 그것을 만든 것이 (가능성이 더 높음) 완전히 다른 스레드 일 수 있습니다. 이 문제를 어떻게 해결합니까?
George Mauer

네 맞습니다. 문제를 해결해야하는 조건으로 게시물을 편집했습니다.
Shea

나는 이것이 사실이라고 확신하지 않는다. Arnshea라는 귀하의 의견에 따라 내 질문을 업데이트했습니다.
Greg D

이해가 안 돼요. 표시 할 창이 필요합니다. IsHandleCreated가 거짓 인 이유는 확실하지 않지만 창을 표시하지 않는 것은 옵션이 아닙니다. 제 질문은 왜 세상에서 거짓인지에 대한 것입니다
George Mauer

핸들이 닫혔거나 컨트롤이 삭제 된 경우 IsHandleCreated가 false를 반환 할 것이라고 믿습니다. 이전에 존재했지만 더 이상 존재하지 않는 컨트롤에 대한 비동기 호출에 물린 것이 아니라고 확신하십니까?
Greg D

3

를 사용하여 Control표시하거나 다른 작업을 수행하기 전에 다른 스레드에서 를 사용하려는 Control경우 생성자 내에서 핸들을 강제로 생성하는 것이 좋습니다. 이것은 CreateHandle함수를 사용하여 수행됩니다.

"컨트롤러"논리가 WinForm에없는 다중 스레드 프로젝트에서이 함수는 Control생성자에서이 오류를 방지하는 도구입니다 .


3

메소드 호출을 호출하기 전에 다음을 추가하십시오.

while (!this.IsHandleCreated) 
   System.Threading.Thread.Sleep(100)

1

다음과 같이 생성자에서 연결된 컨트롤의 핸들을 참조합니다.

참고 :이 솔루션에주의 하십시오 . 컨트롤에 핸들이있는 경우 크기 및 위치 설정과 같은 작업을 수행하는 것이 훨씬 느립니다. 이것은 InitializeComponent를 훨씬 느리게 만듭니다 . 더 나은 해결책은 컨트롤이 핸들을 갖기 전에 아무것도 배경으로하지 않는 것입니다.


0

나는 이런 종류의 간단한 형태 로이 문제가 있었다.

public partial class MyForm : Form
{
    public MyForm()
    {
        Load += new EventHandler(Form1_Load);
    }

    private void Form1_Load(Object sender, EventArgs e)
    {
        InitializeComponent();
    }

    internal void UpdateLabel(string s)
    {
        Invoke(new Action(() => { label1.Text = s; }));
    }
}

그런 다음 n다른 비동기 스레드의 new MyForm().UpdateLabel(text)경우 UI 스레드를 호출하는 데 사용 했지만 생성자가 UI 스레드 인스턴스에 핸들을 제공하지 않으므로 다른 스레드는 Object reference not set to an instance of an object또는 Invoke or BeginInvoke cannot be called on a control until the window handle has been created. 이 문제를 해결하기 위해 정적 개체를 사용하여 UI 핸들을 유지했습니다.

public partial class MyForm : Form
{
    private static MyForm _mf;        

    public MyForm()
    {
        Load += new EventHandler(Form1_Load);
    }

    private void Form1_Load(Object sender, EventArgs e)
    {
        InitializeComponent();
        _mf = this;
    }

    internal void UpdateLabel(string s)
    {
        _mf.Invoke((MethodInvoker) delegate { _mf.label1.Text = s; });
    }
}

지금까지 잘 작동하는 것 같아요.


0
var that = this; // this is a form
(new Thread(()=> {

    var action= new Action(() => {
       something
    }));

    if(!that.IsDisposed)
    {
        if(that.IsHandleCreated)
        {
            //if (that.InvokeRequired)
                that.BeginInvoke(action);
            //else
            // action.Invoke();
        }
        else
            that.HandleCreated+=(sender,event) => {
                action.Invoke();
            };
    }


})).Start();

이것은 C #- this호출에 따라 다르지 않으며 javascript 스타일 기술이 필요하지 않습니다.
George Mauer

확실히 무엇을 호출할지 명시 적으로 만들려고했습니다. -뭐든지
Shimon Doodkin 19.01.02

0

이건 어떨까요?


    public static bool SafeInvoke( this Control control, MethodInvoker method )
    {
        if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated )
        {
            if( control.InvokeRequired )
            {
                control.Invoke( method );
            }
            else
            {
                method();
            }
            return true;
        }
        else return false;
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.