산으로 가는 Invoke()

DOUIK·2022년 5월 15일
0

원문

https://stackoverflow.com/questions/50884893/calling-vs-invoking-a-function

There are 2 ways to reach place B from your HOME.

  1. Direct/Automatic way (Invoke), i.e. if you choose first way, you do not need to walk. Someone will automatically take you to place B.
  2. Indirect way (Call), i.e. if choose second way, you only need to reach A(by walk). Someone is there at place A to automatically take you to place B.
    Have a look at the below image. I think it will clear your doubt.

요약이미지

In case of Calling, you originally refer to the statement that actually calls the function.

In case of Invoking, you indirectly refer to calling statement to actually invoke/run the function.

정리

  1. 직접/자동 방식(Invoke) 첫 번째 방법을 고르면 걸어갈 필요가 없음. 누군가 자동으로 B까지 데려다 줄 것임
  2. 간접적인 방식(Call) 두 번째 방법을 고르면 A에 (걸어서) 가기만 하면 됨. A에 있는 누군가가 널 B까지 데려다 줌

Calling은 실제 함수를 호출하는 구문을 참조하고 Invoking은 호출하는 구문을 간접적으로 참조해서 실제 함수를 호출/실행한다.


사실 찾고 싶었던건 그냥 함수 호출하는거 하고 .Invoke()해서 호출하는 것의 차이를 알고 싶었던 건데 이건 용어에 대한 설명인것 같다..

자바에선 함수를 불러올 때 그냥 함수 이름 쓰는거 말고 call()이나 apply() 메소드를 사용해서 호출할 수도 있다.

function();
function.call();
function.apply();

그래서 일반적으로 메소드를 사용하지 않으면 직접적으로 호출하니 call이고, call, apply등의 메소드를 사용해서 간접적으로 호출하면 invoke 인듯 한다

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-

과거에 내가 겁나 사용했던 null 조건부 연산자 ?. ?[] 이런 애들이 사용하는 Invoke()는 뭘까

Invoke를 검색해보면 크로스 스레드...Form 기반... 이런 말들이 쏟아져 나오는데 Unity에서 사용하는 Invoke는 이것이다.

MethodBase.Invoke 메서드

https://docs.microsoft.com/ko-kr/dotnet/api/system.reflection.methodbase.invoke?view=net-6.0

전에 유니티를 배울 때 Invoke를 사용하면 Reflection을 이용하기 때문에 속도가 느리고.. 사용을 권장하진 않았던 기억이 난다.

https://3dmpengines.tistory.com/1728

프로그램 언어에서 Reflection은 Runtime 시에 Object 객체의 형태나 타입에 대한 정보를 읽어내는 것을 말한다. C++에서는 제한적이지만 RTTI(Run-time type information) 매커니즘을 통해서 dynamic_cast, typeid 같은 Reflection 기능을 제공하고 있다고 한다.

C#의 Reflection은 Instance Type 체크하거나 Field 정보를 알아오는 것은 물론이고 Method를 실행하기도 하고 Field나 Method를 동적으로 추가할 수도 있다. 다만 실시간으로 체크하기 때문에 효율적인 면에서는 Static보다 느리다. 실제로 속도 면에서는 Direct Access하는 것보다 수천배 느리다.

또한 iOS의 경우 자체 저수준 가상머신인 LLVM 환경에서 제한적으로 Reflection을 허용하는데, Mono Runtime에 맞도록 미리 컴파일 하는 AOT(Ahead of Time) 컴파일 방식을 채용하고 있어 동적인 코드를 생성 할 수 없다. (Field 타입 받아오거나 Method 실행은 가능한듯)

https://www.jacksondunstan.com/articles/2972

델리게이트 사용할 때 많이 썼었는데

그리고 아까 검색할 때 나왔던 Invoke는 저것이 아닌것 같다.

Control.Invoke 메서드

https://docs.microsoft.com/ko-KR/dotnet/api/system.windows.forms.control.invoke?view=windowsdesktop-6.0

.NET의 Invoke 함수는 어떤 접근 제어 지시자를 사용했던 간에 상대 클래스 내에 있는 함수를 무조건 실행한다. 다만 System.Windows.Form 네임스페이스에 있는 Control 기반의 Windows 개체에만 동작한다.

(다른 네임 스페이스에 있는 다른 함수였다..)

이게 뭔 소린고..를 얘기하기 전에 기본적으로 하나의 응용프로그램은 단일 스레드로 구성되어 있기 때문에 for문이나 while문 같은 반복문의 처리가 길어지면 다른 작업을 수행할 수 없다.

https://ddochea.tistory.com/11?category=568955

위 사이트의 예제를 보면 입력한 수 만큼 x에 2를 더하는 프로그램이 있는데 사용자가 여기에 엄청 큰 수를 넣으면 그 시간동안 메인 스레드가 UI 컨트롤을 핸들링하지 않아 UI가 멈추게 된다. 그래서 병렬처리를 위해 새로운 스레드를 만드는데 C#에선 이를 위한 Thread 함수를 지원한다.

Thread thread = new Thread(new ThreadStart(delegate() // thread 생성 
{ 
	// ThreadStart 델리게이트를 통해 해당 Thread 가 실행할 작업(Method)을 위임(delegate) 함. 
	var result = Calc(cnt); 
    lbl_Result.Text = result.ToString(); // error!! 크로스 스레드 작업 
    })); 
thread.Start(); // thread 실행하여 병렬작업 시작
}

그러나 ThreadStart 델리게이트로 계산 작업을 위임하고 그 작업 결과를 ToString() 함수를 통해 현재 스레드에 있는 변수에 값을 전달하려고 하면 크로스 스레드(Cross-Thread) 오류가 뜬다.

메인 스레드가 아닌 곳에서 직접 메인 스레드에 있는 컨트롤에 액세스하려고 할 때 크로스 스레드 예외가 발생한다. 여기에서 Invoke 함수를 사용한다

Thread thread = new Thread(new ThreadStart(delegate() // thread 생성 
{ 
	// ThreadStart 델리게이트를 통해 해당 Thread 가 실행할 작업내용을 선언 
    // ThreadStart는 Java의 Runnable 인터페이스와 비슷한 개념이다. 
    var result = Calc(cnt); 
    this.Invoke(new Action(delegate() 
    // this == Form 이다. Form이 아닌 컨트롤의 Invoke를 직접호출해도 무방하다. 
    { 
    	//Invoke를 통해 lbl_Result 컨트롤에 결과값을 업데이트한다. 
        lbl_Result.Text = result.ToString(); 
    })); 
})); 

thread.Start(); // thread 실행하여 병렬작업 시작

그렇다면 어떻게 Invoke는 크로스 스레드 문제를 해결했을까?

https://blog.hexabrain.net/326

우리가 다른 스레드를 통해 컨트롤에 접근해야 하는 경우 Invoke() 메서드를 통해 UI 스레드에게 "시간이 좀 날때 이 일좀 처리해줘!"라고 메시지를 보낼 수 있다. 실제로 UI 스레드는 자신에게 보내는 모든 메시지를 계속해서 처리할 수 있도록 메시지 펌프(message pump)를 가지고 있다. 즉, 우리가 넘겨준 델리게이트가 해당 컨트롤을 만든 UI 스레드에서 실행된다는 얘기다.

여기서 UI 스레드는 UI 컨트롤을 생성한 스레드이고 이런 UI 컨트롤을 갖지 않는 스레드를 작업 스레드라고 한다.

컨트롤의 메서드 및 속성의 대부분은 UI 스레드에서만 액세스할 수 있다. 왜냐하면 각 스레드마다 메시지 루프(GetMessage()가 스레드로 메시지를 필터링)가 있어야 하고, 컨트롤을 사용하여 작업을 수행하려면 작업 스레드에서 UI 스레드로 메시지를 전달해야 하기 때문이다. .NET에서는 각 컨트롤들이 Invoke, BeginInvoke, EndInvoke같은 메서드들을 상속받기 때문에 쉽다.

GUI Windows 응용 프로그램은 메시지 루프가 있는 윈도우 프로시저를 기반으로 한다. C로 응용 프로그램을 작성하면 다음과 같다.

MSG message;
while (GetMessage(&message, NULL, 0, 0))
{
    TranslateMessage(&message);
    DispatchMessage(&message);
}

위 코드를 사용해서 애플리케이션은 메시지를 기다린 다음 윈도우 프로시저에 메시지를 전달한다. 윈도우 프로시저는 메시지(WM_)을 확인하고 처리하는 거대한 switch/case 구문이다.

내가 가지고 있는 작업 스레드에서 어떻게 UI 스레드를 호출할 수 있을까? 절차를 단순화하면 다음과 같다.

  • invoke할 함수의 (thread-safe) queue를 만든다.
  • 윈도우 프로시저에 사용자 지정 메시지를 전달한다. 이 대기열을 우선 순위 대기열로 설정하면 이러한 호출에 해단 우선순위도 결정할 수 있다. (ex. 작업 스레드의 진행률 알림이 경보 알림보다 낮은 우선 순위를 가질 수 있음)
  • (switch/case문 내부의) 윈도우 프로시저에서 전달한 메시지를 이해하고 queue에서 호출하는 함수를 참조하고 그것을 실행(invoke)할 수 있다.

WPF와 WinForms 모두 이 방법을 통해 스레드에서 메인 스레드로 메시지를 전달(dispacth)한다.

참고 : https://www.hind.pe.kr/1137 (여기 스레드 그림 확인)
https://stackoverflow.com/questions/10170448/how-to-invoke-a-ui-method-from-another-thread
https://www.csharpstudy.com/Threads/uithread.aspx

0개의 댓글