윈도우에 있는 ini파일이 개행이 \r\n이거니와.
리눅스쪽으로 복사가 되면서 더미 데이터가 들어가는것 같다..
it블로그
초기 하드웨어 점검: (쿨링패드장착, IO핀, 하이닉스M.2 SSD설치)
최초에 우분투서버 이미지를 SD카드에 삽입
Etcher나 Rufus를 이용하여 SD카드에 플레싱후 부팅
맥주소를 확인하여 ipv4주소로 접속한다.
명령어 (nmap -p 22 --open 192.168.0.1/24)
SSH 포트(22) 활성화 확인후 putty로 접속확인
sftp://[기기아이피]로 파일질라를 통해 포트(22) 접속확인
사용자명(U): orangepi
비밀번호(P): orangepi
SD카드와 SSD스토리지를 동시에 장착하고 부팅
#아래의 명령으로 플레싱툴 설치
sudo apt update
sudo apt install -y bmap-tools
# 처음 부팅된 SD카드에 플레싱할 img파일을 복사한후
# 터미널에서 다음명령어로 SSD 장착여부 확인
lsblk
# 위결과가 nvme0n1 이나 sda로 나옴
# bmaptool으로 img를 ssd에 카피
sudo bmaptool copy /home/orangepi/ubuntu.img /dev/nvme0n1 --nobmap
# 이후 orangepi-config 등에서 부팅 시퀀스 잡아주기
System → Install to/update boot loader 메뉴로 진입
Install/Update bootloader on SPI Flash 옵션을 선택하면
얼마 시간이 지난후 다시 메뉴로 돌아감 이후 재부팅하면 칩셋에 SSD를 1순위로 부팅
명령어(sudo orangepi-config)로 → 타임존, 로케일 설정
Personal settings 하위메뉴에서
Timezone: Asia / Seoul 설정
Locales: en_US.UTF-8, ko_KR.EUC-KR, ko_KR.UTF-8 설정
전원 인가시 표시되는 텍스트들을 없에고 이미지 한장으로 표시
#부팅로고 활성화
sudo nano /boot/orangepiEnv.txt
#내용중에 위 2줄을 다음처럼 변경
verbosity=0
bootlogo=true
#이후 테마 디렉터리 생성
sudo mkdir -p /usr/share/plymouth/themes/mylogo
#/usr/share/plymouth/themes/mylogo 폴더로 logo.png 파일을 복사한다.
#mylog.plymouth 생성
sudo nano /usr/share/plymouth/themes/mylogo/mylogo.plymouth
#내용은 다음으로
[Plymouth Theme]
Name=MyLogo
Description=Simple Logo Boot Screen
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/mylogo
ScriptFile=/usr/share/plymouth/themes/mylogo/mylogo.script
#mylogo.script 생성
sudo nano /usr/share/plymouth/themes/mylogo/mylogo.script
#내용은 다음으로
logo = Image("logo.png");
sprite = Sprite(logo);
sprite.SetX(
Window.GetWidth()/2 -
logo.GetWidth()/2
);
sprite.SetY(
Window.GetHeight()/2 -
logo.GetHeight()/2
);
#테마 활성화
sudo ln -sf \
/usr/share/plymouth/themes/mylogo/mylogo.plymouth \
/etc/alternatives/default.plymouth
#initramfs 갱신
sudo update-initramfs -u
sudo reboot
기기 업데이트 및 폴더생성과 바로가기 만들기
sudo apt update (저장소 업데이트)
sudo apt upgrade (업그레이드 시간오래걸림)
sudo apt update (업데이트 다시)
mkdir temp (temp폴더 생성)
mkdir MainApp (MainApp폴더 생성)
mkdir SubApp (SubApp폴더 생성)
mkdir WatchDog (SubApp폴더 생성)
touch ~/MainApp/start_main.sh (MainApp 바로가기 만들기)
touch ~/SubApp/start_sub.sh (SubApp 바로가기 만들기)
touch ~/WatchDog/toggle_watch_signal.sh (WatchDog Toggle 바로가기 만들기)
chmod +x ~/MainApp/start_main.sh (MainApp 바로가기 실행가능 하도록 변경)
chmod +x ~/SubApp/start_sub.sh (SubApp 바로가기 실행가능 하도록 변경)
chmod +x ~/WatchDog/toggle_watch_signal.sh (워치독 시그널 바로가기 실행가능 하도록 변경)
X-Server 그래픽 엔진과 경량 윈도우매니저 OpenBox 설치
# 패키지 저장소 새로고침
sudo apt update
# X11, 디스플레이 매니저, 오픈박스 설치
sudo apt install xorg lightdm openbox -y
한글폰트 및 한글입력기(fcitx5) 설치 및 환경변수
# 한글 폰트 및 fcitx5 입력기 패키지 설치
sudo apt install fonts-nanum fonts-noto-cjk im-config fcitx5 fcitx5-hangul fcitx5-config-qt fcitx5-frontend-gtk2 fcitx5-frontend-gtk3 fcitx5-frontend-qt5 -y
# 입력기 프레임워크를 fcitx5로 지정
im-config -n fcitx5
# 리눅스 시스템 인코딩 환경에 한국어 언어 팩 생성 및 고정
sudo locale-gen ko_KR.UTF-8
sudo update-locale LANG=ko_KR.UTF-8 LANGUAGE=ko_KR:ko LC_ALL=ko_KR.UTF-8
# LightDM 로그인 세션 환경 변수 설정
nano ~/.xprofile 에 아래 내용 추가 (없으면 새로 생성)
export LANG=ko_KR.UTF-8
export LC_ALL=ko_KR.UTF-8
export XMODIFIERS=@im=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
# 환경변수 파일 수정처리
sudo nano /etc/environment
# 제일 하단에 다음을 입력후 저장
LANG=ko_KR.UTF-8
LANGUAGE=ko_KR:ko
LC_ALL=ko_KR.UTF-8
한글입력기(fcitx5) 프로파일을 구성한다.
# 프로파일 내용진입 하여 다음 내용으로 수정
mkdir ~/.config/fcitx5
sudo nano ~/.config/fcitx5/profile
[Groups/0]
# Group Name
Name=Default
# Layout
Default Layout=us
# Default Input Method
DefaultIM=keyboard-us
[Groups/0/Items/0]
# Name
Name=keyboard-us
# Layout
Layout=
[Groups/0/Items/1]
# Name
Name=hangul
# Layout
Layout=
[GroupOrder]
0=Default
원격지 화면 통제를 위해 VNC 서버를 설치하고 비밀번호를 고정
# x11vnc 패키지 엔진 설치
sudo apt install x11vnc -y
# 계정 홈 디렉토리 내부에 보안 인증서 폴더 생성
mkdir -p ~/.vnc
# 비밀번호 생성 및 저장 (패스워드 2회입력후, 저장여부 Y)
x11vnc -storepasswd ~/.vnc/passwd
자동 로그인 orangepi로 로그인을 락인 시킴
# 구성 파일 에디터 열기
sudo nano /etc/lightdm/lightdm.conf
# 아래 내용으로 설정한후 저장
[Seat:*]
autologin-user=orangepi
autologin-user-timeout=0
user-session=openbox
콘솔 기반 구동 시 가상 모니터를 생성하여 Headless 상태에서도 해상도 및 VNC 유지
# 가상 그래픽 드라이버 패키지 설치
sudo apt install xserver-xorg-video-dummy -y
# X11 가상 디스플레이 구성 파일 생성
sudo nano /etc/X11/xorg.conf.d/99-dummy.conf
# 아래 설정 내용 복사 후 저장 (1280x720 HD 지정)
Section "Device"
Identifier "DummyDevice"
Driver "dummy"
EndSection
Section "Monitor"
Identifier "DummyMonitor"
HorizSync 28.0-80.0
VertRefresh 48.0-75.0
EndSection
Section "Screen"
Identifier "DummyScreen"
Device "DummyDevice"
Monitor "DummyMonitor"
DefaultDepth 24
SubSection "Display"
Depth 24
Modes "1280x720"
EndSubSection
EndSection
오픈박스가 로드될때 순차적으로 자동 실행 (이후 재부팅: sudo reboot)
# 오픈박스 스타트업 구성 파일 폴더 및 파일 생성
mkdir -p ~/.config/openbox
sudo nano ~/.config/openbox/autostart
[ 아래 내용으로 설정한 후 저장 ]
# 1. 마우스 커서 기본 모양 설정
xsetroot -cursor_name left_ptr &
# 2. VNC 원격 서버 백그라운드 실행
x11vnc -auth guess -forever -loop -noxdamage -repeat -rfbauth /home/orangepi/.vnc/passwd -rfbport 5900 -shared &
# 3. 절전모드 사용안함
xset s off &
xset -dpms &
xset s noblank &
setterm -blank 0 -powersave off -powerdown 0 &
# 4. fcitx5 한글 입력기 데몬 실행
fcitx5 -d &
# 5. 시스템 안정화 후 메인 앱 자동 구동 (1초 대기 후 실행)
# 워치독을 데몬 서비스를 실행하면서 워치독을 통해 메인앱을 실행하고자 하는 경우 아래내용은 기재하지 않음
sleep 1
/home/orangepi/MainApp/start_main.sh &
오픈박스가 로드될때 마우스와 키보드로 처리할 내용
(Ctrl+Shift+M:메인앱, Ctrl+Shift+S:보조앱, Ctrl+Shift+T:워치독토글, Ctrl+Alt+Delete:작업관리자, 기타등등..)
# 시스템 공용 폴더에서 원본파일을 유저 폴더로 복사
cp /etc/xdg/openbox/rc.xml ~/.config/openbox/rc.xml
# 설정파일을 아래의 xml로 변경처리
# 타이핑 보다는 파일질라로 복사처리 권장
[ 아래의 사항으로 변경 ]
<?xml version="1.0" encoding="UTF-8"?>
<openbox_config xmlns="http://openbox.org/3.4/rc" xmlns:xi="http://www.w3.org/2001/XInclude">
<resistance><strength>10</strength><screen_edge_strength>20</screen_edge_strength></resistance>
<focus><focusNew>yes</focusNew><followMouse>no</followMouse><focusLast>yes</focusLast><underMouse>no</underMouse><focusDelay>200</focusDelay><raiseOnFocus>no</raiseOnFocus></focus>
<placement><policy>Smart</policy><center>yes</center><monitor>Primary</monitor><primaryMonitor>1</primaryMonitor></placement>
<theme>
<name>Clearlooks</name><titleLayout>NLIMC</titleLayout><keepBorder>yes</keepBorder><animateIconify>yes</animateIconify>
<font place="ActiveWindow"><name>sans</name><size>8</size><weight>bold</weight><slant>normal</slant></font>
<font place="InactiveWindow"><name>sans</name><size>8</size><weight>bold</weight><slant>normal</slant></font>
<font place="MenuHeader"><name>sans</name><size>9</size><weight>normal</weight><slant>normal</slant></font>
<font place="MenuItem"><name>sans</name><size>9</size><weight>normal</weight><slant>normal</slant></font>
</theme>
<desktops>
<number>1</number><firstdesk>1</firstdesk><names></names><popupTime>0</popupTime>
</desktops>
<resize><drawContents>yes</drawContents><popupShow>Nonpixel</popupShow><popupPosition>Center</popupPosition></resize>
<margins><top>0</top><bottom>0</bottom><left>0</left><right>0</right></margins>
<keyboard>
<chainQuitKey>C-g</chainQuitKey>
<keybind key="W-d"><action name="ToggleShowDesktop"/></keybind>
<keybind key="A-F4"><action name="Close"/></keybind>
<keybind key="A-Escape"><action name="Lower"/><action name="FocusToBottom"/><action name="Unfocus"/></keybind>
<keybind key="A-space"><action name="ShowMenu"><menu>client-menu</menu></action></keybind>
<keybind key="Print"><action name="Execute"><command>scrot</command></action></keybind>
<keybind key="C-S-m">
<action name="Execute">
<command>/home/orangepi/MainApp/start_main.sh</command>
</action>
<action name="Focus"/>
<action name="Raise"/>
</keybind>
<keybind key="C-S-s">
<action name="Execute">
<command>/home/orangepi/SubApp/start_sub.sh</command>
</action>
<action name="Focus"/>
<action name="Raise"/>
</keybind>
<keybind key="C-S-t">
<action name="Execute">
<command>/home/orangepi/WatchDog/toggle_watch_signal.sh</command>
</action>
<action name="Focus"/>
<action name="Raise"/>
</keybind>
<keybind key="C-A-Delete">
<action name="Execute">
<command>lxterminal --title="Task Manager" -e htop</command>
</action>
<action name="Focus"/>
<action name="Raise"/>
</keybind>
</keyboard>
<mouse>
<dragThreshold>1</dragThreshold><doubleClickTime>500</doubleClickTime><screenEdgeWarpTime>0</screenEdgeWarpTime><screenEdgeWarpMouse>false</screenEdgeWarpMouse>
<context name="Frame">
<mousebind button="A-Left" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
<mousebind button="A-Left" action="Drag"><action name="Move"/></mousebind>
<mousebind button="A-Right" action="Drag"><action name="Resize"/></mousebind>
</context>
<context name="Titlebar">
<mousebind button="Left" action="Drag"><action name="Move"/></mousebind>
<mousebind button="Left" action="DoubleClick"><action name="ToggleMaximize"/></mousebind>
</context>
<context name="Client">
<mousebind button="Left" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
<mousebind button="Middle" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
<mousebind button="Right" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
</context>
<context name="Desktop">
<mousebind button="Left" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
<mousebind button="Right" action="Press"><action name="Focus"/><action name="Raise"/></mousebind>
</context>
<context name="Root">
<mousebind button="Right" action="Press"><action name="ShowMenu"><menu>root-menu</menu></action></mousebind>
</context>
</mouse>
<menu><file>menu.xml</file><hideDelay>200</hideDelay><middle>no</middle><submenuShowDelay>100</submenuShowDelay><submenuHideDelay>400</submenuHideDelay><showIcons>yes</showIcons><manageDesktops>no</manageDesktops></menu>
</openbox_config>
장비의 물리적 시간 오차방지용 타임데몬 Chrony 설치 및 락인
# 1. 크로니 패키지 설치 및 활성화
sudo apt update
sudo apt install chrony -y
sudo systemctl enable chrony
sudo systemctl start chrony
# 2. 타임 서버 설정 수정 (인터넷 가능 환경 또는 현장 NTP 서버 지정)
sudo nano /etc/chrony/chrony.conf
# [기존 pool 대신에 한국 표준 타임 서버 등록 (지우고 아래로 변경)]
pool time.bora.net iburst
pool time.kornet.net iburst
pool ntp.gwnn.rm.kr iburst
# 3. 크로니 데몬 재시작 및 동기화 상태 현장 검증
sudo systemctl restart chrony
chronyc tracking (현재 시간 동기화 추적 상태 확인)
chronyc sources -v (연결된 외부 타임 서버 리스트 및 상태 확인)
보조 유틸리티 설치
# 정비용 유틸리티 (메모장, 터미널, 탐색기, 파일질라, 이미지뷰어) 설치
sudo apt update
sudo apt install -y mousepad lxterminal pcmanfm filezilla viewnior scrot network-manager-gnome
오픈박스 바탕화면에 나올 메뉴 구성 및 sh 스크립트 연동
# 메뉴판 구성 파일 에디터 열기
nano ~/.config/openbox/menu.xml
# [아래의 내용으로 변경]
<?xml version="1.0" encoding="utf-8"?>
<openbox_menu xmlns="http://openbox.org/3.4/menu">
<menu id="root-menu" label="EQUIPMENT CONTROL">
<separator label="[ RUN PROGRAM ]" />
<item label="메인 App">
<action name="Execute"><command>/home/orangepi/MainApp/start_main.sh</command></action>
</item>
<item label="보조 App">
<action name="Execute"><command>/home/orangepi/SubApp/start_sub.sh</command></action>
</item>
<separator label="[ REPAIR TOOL ]" />
<item label="메모장">
<action name="Execute"><command>mousepad</command></action>
</item>
<item label="터미널">
<action name="Execute"><command>lxterminal</command></action>
</item>
<item label="파일 탐색기">
<action name="Execute"><command>pcmanfm</command></action>
</item>
<separator />
<item label="장비 재부팅 (Reboot)">
<action name="Execute"><command>systemctl reboot</command></action>
</item>
</menu>
</openbox_menu>
한국어 입력 자판 GUI 설정 (configtool 이용)
# 바탕화면 터미널 메뉴로 키보드 설정진입
fcitx5-configtool
# 좌측 입력기에 다음 순서로 배치처리
키보드 - Korean - Korean (101/104)
한글(Hangul)
# 이렇게 설정하고 Apply
메인 앱 실행 스크립트 작성
# 메인 앱 디렉토리로 이동 및 스크립트 생성
cd ~/MainApp
nano start_main.sh
# [아래의 내용으로 변경]
#!/bin/bash
export DISPLAY=:0
export XMODIFIERS=@im=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
# 프로그램 실행 경로로 이동
cd /home/orangepi/MainApp
# 메인 애플리케이션 실행 (실제 구동할 바이너리나 파이썬 파일 지정)
# 예: python3 main_gui.py 또는 ./.NET바이너리 등
/usr/bin/python3 main_gui.py
# 작성 완료 후 저장(Ctrl+O, Enter, Ctrl+X)
서브 앱 실행 스크립트 작성
# 서브 앱 디렉토리로 이동 및 스크립트 생성
cd ~/SubApp
nano start_sub.sh
# [아래의 내용으로 변경]
#!/bin/bash
export DISPLAY=:0
export XMODIFIERS=@im=fcitx
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
# 프로그램 실행 경로로 이동
cd /home/orangepi/SubApp
# 메인 애플리케이션 실행 (실제 구동할 바이너리나 파이썬 파일 지정)
# 예: python3 main_gui.py 또는 ./.NET바이너리 등
/usr/bin/python3 sub_gui.py
# 작성 완료 후 저장(Ctrl+O, Enter, Ctrl+X)
와치독앱을 데몬서비스로 등록하여 죽지않도록 설정
# 서비스 파일 생성
sudo nano /etc/systemd/system/mywatchdog.service
# [아래의 내용으로 변경]
[Unit]
Description=System WatchDog Service
After=graphical.target
[Service]
Type=simple
User=orangepi
WorkingDirectory=/home/orangepi/WatchDog
ExecStart=/usr/bin/python3 /home/orangepi/WatchDog/myWatchDog.py
Restart=always
RestartSec=3
Environment=DISPLAY=:0
[Install]
WantedBy=graphical.target
#서비스 활성화 및 가동
sudo systemctl daemon-reload
sudo systemctl enable mywatchdog.service
sudo systemctl start mywatchdog.service
파이썬을 이용하여 워치독을 처리함 (화면에 상관없이 Ctrl+Shift+T)
# 파이썬 pip 모듈 업데이트
sudo apt update
sudo apt install -y python3-pip python3-setuptools
# 파이썬 기본 GUI TKinter 설치
sudo apt install -y python3-tk
# 워치독 스크립트 파일 생성
(WatchAppName.txt 파일에는 '프로세스명|파라미터명' 으로 기입)
touch ~/WatchDog/WatchAppName.txt
touch ~/WatchDog/myWatchDog.py
touch ~/WatchDog/toggle_watch_signal.py
# 초기에 생성했던 toggle_watch_signal.sh 파일에 내용넣기
sudo nano ~/WatchDog/toggle_watch_signal.sh
# [ 파일내용은 아래의 내용으로 기재 ]
#!/bin/bash
/usr/bin/python3 /home/orangepi/WatchDog/toggle_watch_signal.py
# [toggle_watch_signal.py] 파일내용 (복사추천)
import os
import sys
import signal
import subprocess
#
TARGET_SCRIPT_SIGNATURE = "myWatchDog.py"
#
try:
output = subprocess.check_output(["ps", "-ef"]).decode('utf-8')
daemon_pid = None
#
for line in output.splitlines():
if TARGET_SCRIPT_SIGNATURE in line and "toggle_watch_signal" not in line:
parts = line.split()
if len(parts) > 1:
daemon_pid = int(parts[1])
break
#
if daemon_pid:
os.kill(daemon_pid, signal.SIGUSR1)
print(f"(PID: {daemon_pid}에 시그널 전송 완료", flush=True)
else:
print(f"실행중인 {TARGET_SCRIPT_SIGNATURE} 스크립트를 찾을 수 없습니다.", flush=True)
except Exception as e:
print(f"시그널 전송 실패: {e}", file=sys.stderr, flush=True)
sys.exit(1)
# [myWatchDog.py] 파일내용 (복사추천)
import os
import sys
import time
import signal
import subprocess
import tkinter as tk
# 설정 파일 경로
CONFIG_FILE = "/home/orangepi/WatchDog/WatchAppName.txt"
WATCH_APP_CMD = "bash /home/orangepi/MainApp/start_main.sh"
# 초기 실행 시 감시할 프로세스 이름 읽기
if not os.path.exists(CONFIG_FILE):
print(f"에러: {CONFIG_FILE} 파일이 존재하지 않습니다.")
sys.exit(1)
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
config_content = f.read().strip()
if not config_content:
print("에러: WatchAppName.txt 파일이 비어 있습니다.")
sys.exit(1)
# '|' 기호로 쪼갠 후 필터링 처리
WATCH_KEYWORDS = [kw.strip() for kw in config_content.split('|') if kw.strip()]
if not WATCH_KEYWORDS:
print("유효한 감시 키워드가 없습니다.")
sys.exit(1)
# 감시 활성화 상태 플래그
is_watching = True
is_osd_running = False
# 해당 키워드의 프로세스가 돌고 있는지 확인
def is_process_running(keywords):
try:
output = subprocess.check_output(["ps", "-ef"]).decode('utf-8')
for line in output.splitlines():
# 자기 자신이나 좀비 프로세스는 필터링에서 예외 처리
if "myWatchDog" in line or "defunct" in line:
continue
#
match_all = True
for kw in keywords:
if kw not in line:
match_all = False
break
#
if match_all:
return True
except Exception:
pass
return False
# 화면 중앙에 잠깐 떴다 사라지는 OSD 레이어
def show_osd_message(message, color):
global is_osd_running
is_osd_running = True
root = tk.Tk()
root.overrideredirect(True)
root.attributes("-topmost", True)
w, h = 420, 80
x = (root.winfo_screenwidth() - w) // 2
y = (root.winfo_screenheight() - h) // 2
root.geometry(f"{w}x{h}+{x}+{y}")
#
label = tk.Label(root, text=message, font=("NanumGothic", 15, "bold"), bg="#1e1e1e", fg=color, bd=2, relief="solid")
label.pack(fill="both", expand=True)
#
def on_destory():
global is_osd_running
root.destroy()
is_osd_running = False
root.after(1200, on_destory)
root.mainloop()
# 시그널 수신 시 처리 핸들러
def handle_toggle_sign(signum, frame):
global is_watching, is_osd_running
#
if is_osd_running:
print("OSD 연타 시그널 무시")
return
is_watching = not is_watching
if is_watching:
show_osd_message("WatchDog 감시 재개", "#00ffcc")
else:
show_osd_message("WatchDog 일시정지", "#ff3333")
# 메인 루프 가동부
if __name__ == "__main__":
signal.signal(signal.SIGUSR1, handle_toggle_sign)
print(f"WatchDog 감시 가동 시작: {WATCH_KEYWORDS}")
#
while True:
if is_watching:
if not is_process_running(WATCH_KEYWORDS):
print("메인앱 다운 감지! 스크립트 실행...")
subprocess.Popen(WATCH_APP_CMD, shell=True, env=os.environ.copy())
# time.sleep으로 체킹 인터벌 부여 (5초)
time.sleep(5)
.NET에서 async, await, Task가 등장한 이후 비동기 프로그래밍 방식은 크게 변화하였다.
과거에는 스레드 동기화 객체를 사용하여 작업 완료를 기다렸지만, 현재는 Task 기반 비동기 모델을 통해 보다 효율적인 구현이 가능하다.
이번 글에서는 비동기 환경에서 자주 사용되는 시그널링(Signaling) 패턴과 TaskCompletionSource를 활용한 현대적인 구현 방법을 소개한다.
다음과 같은 구조를 생각해보자.
즉, 결과를 기다리는 주체와 결과를 생성하는 주체가 서로 다른 상황이다.
private int _result;
private readonly ManualResetEvent _event = new(false);
private async Task<int> F1()
{
_event.Reset();
await Task.Run(() => _event.WaitOne());
return _result;
}
private void F2(int result)
{
_result = result;
_event.Set();
}
private TaskCompletionSource<int>? _resultTcs;
private Task<int> F1()
{
_resultTcs = new TaskCompletionSource<int>();
return _resultTcs.Task;
}
private void F2(int result)
{
_resultTcs?.TrySetResult(result);
}
int result = await F1();
실제 서비스 코드에서는 RunContinuationsAsynchronously 옵션을 함께 사용하는 것을 권장한다.
private TaskCompletionSource<int>? _resultTcs;
public Task<int> F1()
{
_resultTcs =
new TaskCompletionSource<int>(
TaskCreationOptions.RunContinuationsAsynchronously);
return _resultTcs.Task;
}
기본 TaskCompletionSource는 TrySetResult() 호출 시 await 이후 코드가 같은 스레드에서 즉시 실행될 수 있다.
int result = await F1(); DoSomething();
즉, 결과를 전달하려던 코드가 소비자 측 후속 로직까지 실행하게 될 수 있다.
ManualResetEvent는 스레드를 점유하는 블로킹 방식이다.
TaskCompletionSource는 스레드 점유 없이 결과 완료 시점을 외부에서 제어할 수 있으며, 현대적인 .NET 비동기 프로그래밍에 가장 적합한 시그널링 기법이다.