호출 (대리자)


93

누구든지이 링크에 작성된이 진술을 설명해 주시겠습니까?

Invoke(Delegate):

컨트롤의 내부 창 핸들 을 소유하는 스레드에서 지정된 대리자를 실행합니다 .

아무도 이것이 무엇을 의미하는지 설명 할 수 있습니까? (특히 굵은 글씨) 나는 그것을 명확하게 이해할 수 없습니다


4
이 질문에 대한 대답은 Control.InvokeRequired 속성에 연결되어 있습니다. msdn.microsoft.com/en-us/library/…
dash

답변:


131

이 질문에 대한 답은 C # 컨트롤의 작동 방식에 있습니다.

Windows Forms의 컨트롤은 특정 스레드에 바인딩되며 스레드로부터 안전하지 않습니다. 따라서 다른 스레드에서 컨트롤의 메서드를 호출하는 경우 컨트롤의 호출 메서드 중 하나를 사용하여 적절한 스레드에 대한 호출을 마샬링해야합니다. 이 속성은 호출 메서드를 호출해야하는지 여부를 결정하는 데 사용할 수 있으며, 컨트롤을 소유하는 스레드를 모르는 경우 유용 할 수 있습니다.

에서 Control.InvokeRequired

실제로 Invoke는 호출하는 코드가 컨트롤이 "존재하는"스레드에서 발생하여 교차 스레드 예외를 효과적으로 방지하도록 보장합니다.

역사적 관점에서 .Net 1.1에서는 실제로 허용되었습니다. 이것이 의미하는 바는 모든 백그라운드 스레드에서 "GUI"스레드에서 코드를 실행하고 시도 할 수 있으며 대부분 작동합니다. 때로는 다른 작업을 수행하는 동안 GUI 스레드를 효과적으로 중단했기 때문에 앱이 종료되는 경우가 있습니다. 이것은 Cross Threaded Exception 입니다. GUI가 다른 것을 그리는 동안 TextBox를 업데이트하려고한다고 상상해보십시오.

  • 어떤 조치가 우선합니까?
  • 둘 다 한꺼번에 일어날 수도 있습니까?
  • GUI가 실행해야하는 다른 모든 명령은 어떻게됩니까?

사실상, 예상치 못한 결과가 많이 발생할 수있는 대기열을 중단하고 있습니다. Invoke는 실제로 원하는 작업을 해당 대기열로 가져 오는 "정중 한"방법이며이 규칙은 throw 된 InvalidOperationException을 통해 .Net 2.0부터 적용되었습니다 .

실제로 뒤에서 일어나는 일과 "GUI 스레드"가 무엇을 의미하는지 이해하려면 메시지 펌프 또는 메시지 루프가 무엇인지 이해하는 것이 유용합니다.

이것은 실제로 " What is a Message Pump " 질문에 이미 답변되어 있으며 컨트롤과 상호 작용할 때 사용하는 실제 메커니즘을 이해하기 위해 읽는 것이 좋습니다.

유용한 기타 자료는 다음과 같습니다.

Begin Invoke의 문제

Windows GUI 프로그래밍의 기본 규칙 중 하나는 컨트롤을 만든 스레드 만 해당 내용에 액세스 및 / 또는 수정할 수 있다는 것입니다 (몇 가지 문서화 된 예외 제외). 다른 스레드에서 시도하면 교착 상태에서 예외, 절반 업데이트 된 UI에 이르기까지 예측할 수없는 동작이 발생합니다. 다른 스레드에서 컨트롤을 업데이트하는 올바른 방법은 적절한 메시지를 응용 프로그램 메시지 큐에 게시하는 것입니다. 메시지 펌프가 해당 메시지를 실행하게되면 해당 메시지를 생성 한 동일한 스레드에서 컨트롤이 업데이트됩니다 (메시지 펌프는 기본 스레드에서 실행 됨).

그리고 대표적인 샘플을 사용하여 코드가 많은 개요를 보려면 다음을 수행하십시오.

잘못된 크로스 스레드 작업

// the canonical form (C# consumer)

public delegate void ControlStringConsumer(Control control, string text);  // defines a delegate type

public void SetText(Control control, string text) {
    if (control.InvokeRequired) {
        control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text});  // invoking itself
    } else {
        control.Text=text;      // the "functional part", executing only on the main thread
    }
}

InvokeRequired에 대한 감사를 받으면 이러한 호출을 래핑하기위한 확장 메서드 사용을 고려할 수 있습니다. 이것은 Stack Overflow 질문 Cleaning Up Code Littered with Invoke Required 에서 다룹니다 .

또한 흥미로울 수있는 역사적으로 일어난 일에 대한 추가 기록 도 있습니다.


68

Windows Forms의 컨트롤 또는 창 개체는 핸들 (HWND라고도 함)로 식별되는 Win32 창을 둘러싼 래퍼 입니다. 컨트롤로 수행하는 대부분의 작업은 결국이 핸들을 사용하는 Win32 API 호출이 발생합니다. 핸들은 생성 한 스레드 (일반적으로 주 스레드)가 소유하며 다른 스레드에서 조작해서는 안됩니다. 어떤 이유로 다른 스레드의 컨트롤로 작업을 수행해야하는 경우을 사용 Invoke하여 주 스레드에 사용자를 대신하여 수행하도록 요청할 수 있습니다 .

예를 들어 작업자 스레드에서 레이블의 텍스트를 변경하려면 다음과 같이 할 수 있습니다.

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));

누군가가 이런 일을하는 이유를 설명해 주시겠습니까? this.Invoke(() => this.Enabled = true);뭐든 this에가, 현재의 thread에 반드시 옳은 의미?
Kyle Delaney

1
@KyleDelaney, 개체는 스레드 "내"가 아니며 현재 스레드가 개체를 만든 스레드 일 필요는 없습니다.
Thomas Levesque 2017

24

컨트롤을 수정하려면 컨트롤이 생성 된 스레드에서 수행해야합니다. 이 Invoke메서드를 사용하면 연결된 스레드 (컨트롤의 기본 창 핸들을 소유하는 스레드)에서 메서드를 실행할 수 있습니다.

아래 샘플에서 thread1은 SetText1이 다른 스레드에서 textBox1.Text를 수정하려고하기 때문에 예외를 발생시킵니다. 그러나 thread2에서는 SetText2의 Action이 TextBox가 생성 된 스레드에서 실행됩니다.

private void btn_Click(object sender, EvenetArgs e)
{
    var thread1 = new Thread(SetText1);
    var thread2 = new Thread(SetText2);
    thread1.Start();
    thread2.Start();
}

private void SetText1() 
{
    textBox1.Text = "Test";
}

private void SetText2() 
{
    textBox1.Invoke(new Action(() => textBox1.Text = "Test"));
}

저는이 접근 방식이 정말 마음에 듭니다. 대의원 자연을 숨기지 만 어쨌든 좋은 지름길입니다.
shytikov 2015-08-27

7
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });

System.Action 사람들의 사용은 이전 버전에만 프레임 워크에서 작동 다른 응답 3.5에 제안이 완벽하게 작동
자살 오리너구리

2

실제적으로는 대리자가 주 스레드에서 호출된다는 것을 의미합니다. 이는 Windows 컨트롤의 경우 주 스레드에서 속성을 업데이트하지 않으면 변경 사항이 표시되지 않거나 컨트롤에서 예외가 발생하기 때문에 중요합니다.

패턴은 다음과 같습니다.

void OnEvent(object sender, EventArgs e)
{
   if (this.InvokeRequired)
   {
       this.Invoke(() => this.OnEvent(sender, e);
       return;
   }

   // do stuff (now you know you are on the main thread)
}

2

this.Invoke(delegate)this.Invoke()주 스레드 / 생성 된 스레드 에서 대리자를 인수로 호출하고 있는지 확인하십시오 .

Thumb 규칙은 기본 스레드를 제외하고는 양식 컨트롤에 액세스하지 않는다고 말할 수 있습니다.

Invoke () 사용에 대해 다음 줄이 의미가있을 수 있습니다.

    private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.textBox1.InvokeRequired)
        {   
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.textBox1.Text = text;
        }
    }

스레드 풀 스레드 (예 : 작업자 스레드)를 만들더라도 주 스레드에서 실행되는 상황이 있습니다. 새 스레드를 만들지 않습니다. 추가 명령을 처리하기 위해 주 스레드를 사용할 수 있습니다. 따라서 먼저 현재 실행중인 스레드가 this.InvokeRequiredtrue를 반환 하는 경우 현재 코드가 작업자 스레드에서 실행되고 있으므로 this.Invoke (d, new object [] {text});

그렇지 않으면 UI 컨트롤을 직접 업데이트합니다 (여기에서 메인 스레드에서 코드를 실행하고 있음을 보장합니다.)


1

즉, 백그라운드 작업자 또는 스레드 풀 스레드에서 해당 메서드를 호출하더라도 대리자가 UI 스레드에서 실행됩니다. UI 요소에는 스레드 선호도 가 있습니다 . UI 스레드라는 하나의 스레드와 직접 대화하는 것을 좋아합니다. UI 스레드는 컨트롤 인스턴스를 만든 스레드로 정의 되므로 창 핸들과 연결됩니다. 그러나 그 모든 것이 구현 세부 사항입니다.

요점은 UI 스레드가 아닌 다른 스레드에서 수행 할 수 없기 때문에 UI에 액세스 할 수 있도록 작업자 스레드에서이 메서드를 호출한다는 것입니다 (라벨의 값 변경 등) .


0

Delegate는 본질적으로 인라인 Action또는 Func<T>입니다. 실행중인 메서드의 범위 밖에서 또는 lambdaexpression ( =>)을 사용하여 대리자를 선언 할 수 있습니다 . 메서드 내에서 대리자를 실행하기 때문에 현재 창 / 응용 프로그램에 대해 실행중인 스레드에서 굵게 표시된 비트를 실행합니다.

Lambda 예

int AddFiveToNumber(int number)
{
  var d = (int i => i + 5);
  d.Invoke(number);
}

0

이는 전달한 델리게이트가 Control 개체 (UI 스레드)를 만든 스레드에서 실행됨을 의미합니다.

응용 프로그램이 다중 스레드이고 UI 스레드가 아닌 스레드에서 UI 작업을 수행하려는 경우이 메서드를 호출해야합니다. 다른 스레드의 Control에서 메서드를 호출하려고하면 System.InvalidOperationException입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.