25.10.05 개발일지
[이것이 C#이다 chapter 13, 19]
대리자와 이벤트, 스레드와 테스크
chapter 13
대리자
콜백을 맡아 실행하는 일을 담당한다.
대리자는 메소드에 참조이다.
대리자에 메소드의 주소를 할당한 후 대리자를 호출하면 이 대리자가 메소드를 호출해준다.

대리자는 메소드에 대한 참조이기 때문에 자신이 참조할 메소드의 반환 형식과 매개변수를 명시해줘야 한다.
여기서 MyDelegate는 int, string과 같은 형식이며 진짜로 메소드를 참조하는 그 무엇을 만들려면 MyDelegate의 인스턴스를 따로 만들어야 한다.

: 대리자가 참조할 메소드

: MyDelegate 객체 생성
MyDelegate ( ) 메소드에 Plus ( ) 메소드를 인수로 넘기면 Callback은 Plus( ) 메소드를 참조하고,
Minus( ) 메소드를 넘기면 Minus 메소드를 참조한다.
이때 메소드를 호출하는 Callback을 사용하면 Callback은 현재 자신이 참조하는 주소에 있는 메소드의 코드를 실행하고 그 결과를 호출자에 반환한다.

: 대리자에 의해 메소드가 호출되는 과정
1. 대리자를 선언한다.
2. 대리자의 인스턴스를 생성한다. 인스턴스를 생성할 때는 대리자가 참조할 메소드를 인수로 넘긴다.
3. 대리자를 호출한다.
일반화 대리자
대리자도 보통의 메소드뿐 아니라 일반화 메소드도 참조할 수 있다.
이 경우에는 일반화 메소드를 참조할 수 있도록 형식 매개변수를 이용하여 선언되어야 한다.

대리자 체인
대리자 하나가 메소드 여러 개를 동시에 참조할 수 있다.



다음과 같이 += 연산자를 이용하여 결합할 수 있다.
이렇게 결합해놓은 대리자는 한 번만 호출하면 자신이 참조하고 있는 메소드를 모두 호출한다.


: 다른 방법으로도 대리자 체인을 만들 수 있다.
반대로 대리자 체인에서 특정 대리자를 끊어낼 때는 -= 연산자, Delegate Remove( ) 메소드를 이용한다.
익명 메소드
이름이 없는 메소드를 익명 메소드라고 한다.
대리자를 선언해서 이름이 없고 구현부만 있는 메소드를 참조하여 대리자의 인스턴스를 호출하면 자신이 참조하고 있는 코드를 실행한다.


익명 메소드는 자신을 참조할 대리자의 형식과 동일한 형식으로 선언되어야 한다.
대리자가 참조할 메소드를 넘겨야 할 일이 생겼는데, 이 메소드가 두 번 다시 사용할 일이 없다고 판단될 때 익명 메소드를 사용하면 매우 유용하다.
이벤트
알람 시계처럼 어떤 일이 생겼을 때 이를 알려주는 객체를 만들 때 사용하는 것이 바로 이벤트다.
이벤트의 동작원리는 대리자와 거의 비슷하다.
이벤트는 대리자를 event 한정자로 수식해서 만든다.










대리자와 이벤트
이벤트가 대리자와 가장 크게 다른 점은 바로 이벤트를 외부에서 직접 사용할 수 없다는 것이다.
이벤트는 public 한정자로 선언되어 있어도 자신이 선언된 클래스 외부에서는 호출이 불가능하다.
반면에 대리자는 public이나 internal로 수식되어 있으면 클래스 외부에서라도 얼마든지 호출이 가능하다.
chapter 19
프로세스와 스레드
프로세스 : 실행 파일이 실해오디어 메모리에 적재된 인스턴스
프로세스는 반드시 하나 이상의 스레드로 구성된다.
스레드는 운영체제가 CPU 시간을 할당하는 기본 단위이다.

멀티스레드
멀티스레드의 장점
- 응답성을 높일 수 있다.
: 다른 일을 하면서 스레드를 하나 더 추가하면 사용자로부터 다른 명령을 입력받을 수 있다.
- 자원 공유가 쉽다.
: 프로세스끼리 데이터를 교환하려면 소켓이나 공유 메모리를 이용해야 한다.
반면에 스레드 방식에서는 스레드끼리 코드 내 변수를 같이 사용하는 것만으로도 데이터를 교환할 수 있다.
- 경제적이다.
: 프로세스를 띄우기 위해 메모리와 자원을 할당하는 작업은 비용이 비싼데,
스레드를 띄울 때는 이미 프로세스에 할당된 메모리와 자원을 그대로 사용하므로 비용을 지불하지 않는다.
멀티스레드의 단점
- 구현이 복잡하다.
- 소프트웨어 안정성을 악화시킬 수 있다.
: 멀티 스레드 기반의 소프트웨어에서는 자식 스레드 중 하나에 문제가 생기면 전체 프로세스에 영향을 준다.
- 과용하면 성능이 저하될 수 있다.
: 많은 스레드가 너무 자주 작업 간 전환을 수행하다 보면 실제로 일하는 시간에 비해 작업 간 전환에 사용하 는 시간이 커져 성능이 저하된다.
스레드 종료하기
사용자가 작업 관리자 등을 이용해서 프로세스를 임의로 죽일 수 있지만,
스레드는 그런 식으로 죽일 수 없다.
살아 있는 스레드를 죽이려면 Thread 객체의 Abort( ) 메소드를 호출해줘야 한다.
Abort( ) 메소드를 사용할 때는 고려해야할 사항이 있다.
Abort( ) 메소드가 호출과 동시에 스레드를 즉시 종료하지 않는다.
finally 블록까지 실행한 후에야 해당 스레드는 완전히 종료된다.
Thread.Interrupt( ) 메소드로도 스레드를 종료할 수 있다.
-> 스레드가 동작 중인 상태를 피해서 WaitJoinSleep 상태에 들어갔을 때 스레드를 중지시킨다.
(더 부드러운 방법)
스레드 동기화
동기화 : 스레드들이 순서를 갖춰 자원을 사용하게 하는 것
자원을 한 번에 하나의 스레드가 사용하도록 보장하는 것
lock 키워드와 Monitor 클래스로 동기화할 수 있다.
lock키워드가 사용하기 더 쉽지만 Monitor 클래스가 더 섬세한 동기화 제어 기능을 제공한다.
크리티컬 섹션 : 한 번에 한 스레드만 사용할 수 있는 코드 영역
lock 키워드로 감싸주기만 하면 평범한 코드를 크리티컬 섹션으로 바꿀 수 있다.

: 과도한 동기화 사용은 소프트웨어의 성능 저하에 영향을 끼치기 때문에 동기화를 설계할 때는 크리티컬 섹션을 반드시 필요한 곳에만 사용하는 것이 중요하다.
lock 키워드의 매개변수로 사용하는 객체는 참조형이면 어느 것이든 쓸 수 있지만,
public 키워드 등을 통해 외부 코드에서도 접근할 수 있는 this, Type 형식, string 형식 이 세가지는 사용하지 않는게 좋다.
Monitor.Enter( ), Monitor.Exit( ) 메소드를 사용하면 lock 키워드를 썼을 때와 똑같이 동기화를 할 수 있다.
Monitor.Wait( ), Monitor.Pulse( ) 메소드는 lock 키워드를 사용했을 때보다 더 섬사한 멀티 스레드 간 동기화를 가능하게 해준다.
태스크
비동기 코드를 손쉽게 작성할 수 있도록 도와준다.
병렬처리 vs 비동기 처리
병렬처리 : 하나의 작업을 여러 작업자가 나눠서 수행한 뒤 다시 하나의 결과로 만드는 것
비동기처리 : 작업 A를 시작한 후 A의 결과가 나올 때까지 마냥 대기하는 대신 곧이어 다른 작업을 수행하다가 작업 A가 끝나면 그때 결과를 받아내는 처리 방식
Task<TResult> 클래스
Task<TResult> 클래스는 코드의 비동기 실행 결과를 손쉽게 취합할 수 있도록 도와준다.
Task 클래스가 비동기로 수행할 코드를 Action 대리자로 받는 대신 Func 대리자로 받는다는 점과 결과를 반환받을 수 있다는 게 차이점이다.
Parallel 클래스
병렬처리를 더 쉽게 구현할 수 있게 해준다.

async 한정자와 awail 연산자
async 한정자는 메소드, 이벤트 처리기, 태스크, 람다식 등을 수식함으로써 C# 컴파일러가 이들을 호출하는 코드를 만날 때 호출 결과를 기다리지 않고 바로 다음 코드로 이동하도록 실행 코드를 생성하게 한다.

이렇게 async 한정자로 메소드나 태스크를 수식하기만 하면 비동기 코드가 만들어진다.
다만 반환형식이 Task나 Task<TResult> 또는 void여야 한다는 제약이 있다.


'10월 개발일지' 카테고리의 다른 글
| 25.10.14 개발일지(C# 개인 프로젝트 게임 만들기) (0) | 2025.11.16 |
|---|---|
| 25.10.13 개발일지(이것이 C#이다 chapter 21, 22) (0) | 2025.11.16 |
| 25.10.06 개발일지(이것이 C#이다 chapter 20) (0) | 2025.11.16 |
| 25.10.02 개발일지(이것이 C#이다 chapter 10, 11, 12) (0) | 2025.11.16 |
| 25.10.01 개발일지(이것이 C#이다 chapter 8, 9) (0) | 2025.11.16 |