티스토리 뷰
Fire-and-Forget
"실행하고 잊어버린다" — 작업을 시작한 뒤 결과를 기다리지 않고 바로 다음 코드로 넘어가는 패턴입니다.
핵심 개념
// 일반 await: 완료될 때까지 기다림
await DoSomethingAsync();
// Fire-and-forget: 시작만 하고 기다리지 않음
_ = DoSomethingAsync(); // 또는 그냥 DoSomethingAsync();
호출자는 작업이 언제 끝나는지, 성공했는지, 예외가 났는지 전혀 신경 쓰지 않습니다.
언제 쓰나?
- 로그 전송, 알림, 통계 수집 같이 실패해도 무방한 부수 작업
- 캐시 갱신, 이메일 발송 등 응답 속도가 중요하고 결과는 불필요한 경우
- IoT에서 센서 데이터를 큐에 던지고 바로 다음 측정으로 넘어갈 때
문제점
// ❌ 위험한 fire-and-forget
async void SendTelemetry(Data d) { // async void는 예외가 그냥 터짐
await httpClient.PostAsync(...);
}
// ❌ Task를 그냥 버리면 예외가 조용히 사라짐
Task.Run(() => DoWorkAsync()); // 예외 삼킴
- 예외 무시 — 실패해도 아무도 모름
- async void — 예외가 앱 전체를 죽일 수 있음
- 생명주기 문제 — 앱 종료 시 작업이 중간에 끊김
올바른 사용법
// ✅ 예외를 잡아서 로그라도 남기기
_ = Task.Run(async () => {
try {
await SendTelemetryAsync(data);
}
catch (Exception ex) {
logger.LogError(ex, "Telemetry 전송 실패");
}
});
// ✅ 확장 메서드로 패턴화
public static void FireAndForget(this Task task, ILogger logger = null) {
task.ContinueWith(t => {
if (t.IsFaulted)
logger?.LogError(t.Exception, "Fire-and-forget 실패");
}, TaskContinuationOptions.OnlyOnFaulted);
}
// 사용
SendTelemetryAsync(data).FireAndForget(logger);
비동기 루프에서의 활용
// 센서 폴링 루프에서 전송은 신경 안 쓰는 패턴
while (!cancellationToken.IsCancellationRequested)
{
var reading = await sensor.ReadAsync();
// 전송 결과 기다리지 않고 바로 다음 측정
_ = eventHubClient.SendAsync(reading).ContinueWith(t => {
if (t.IsFaulted) logger.LogWarning("전송 실패");
});
await Task.Delay(100, cancellationToken);
}
고빈도 센서 데이터를 Event Hub로 보내는 상황에서 측정 주기를 전송 지연에 묶어두지 않으려 할 때 유용한 패턴입니다.
반대되는 패턴
1. Await (직접 대기) — 가장 기본
// 완료될 때까지 현재 흐름을 멈춤
var result = await SendTelemetryAsync(data);
Console.WriteLine($"전송 완료: {result.StatusCode}");
결과를 받고, 예외도 정상적으로 전파됩니다. 가장 단순하고 안전합니다.
2. Request-Reply 패턴
// 요청 → 응답을 반드시 받아야 다음으로 진행
var response = await httpClient.PostAsync("/api/data", content);
if (!response.IsSuccessStatusCode)
throw new Exception("전송 실패");
fire-and-forget과 정반대 — 응답이 올 때까지 진행 불가.
3. Acknowledgement (ACK) 패턴
메시지 큐, IoT에서 흔한 패턴입니다.
// 메시지를 보내고, 상대방이 "받았어" 신호를 줄 때까지 대기
var ackReceived = await mqttClient.PublishWithAckAsync(topic, payload);
if (!ackReceived)
await RetryAsync();
Event Hub / MQTT QoS 1~2가 이 방식입니다.
4. Saga / Compensating Transaction
분산 시스템에서 "실패하면 되돌린다"는 보장.
try {
await step1.ExecuteAsync(); // DB 저장
await step2.ExecuteAsync(); // 외부 API 호출
await step3.ExecuteAsync(); // 알림 발송
}
catch {
await step2.CompensateAsync(); // 롤백
await step1.CompensateAsync();
}
5. Outbox 패턴
"보냈다"가 아니라 "반드시 보내진다"를 DB 수준에서 보장.
[비즈니스 로직] → DB 트랜잭션에 메시지도 같이 저장 (Outbox 테이블)
↓
[별도 워커]가 Outbox를 폴링해서 실제 전송
↓
성공하면 Outbox에서 삭제
fire-and-forget의 완전한 반대 — 전송 실패해도 재시도가 보장됩니다.
스펙트럼으로 보면
[보장 약함] ←————————————————————→ [보장 강함]
Fire-and-Forget → Await → ACK → Saga → Outbox
"몰라도 됨" "기다림" "확인" "보상" "반드시 됨"
데이터 종류적합한 패턴
| 고빈도 센서 Raw 데이터 | Fire-and-Forget (유실 허용) |
| 알람/임계값 초과 이벤트 | ACK 패턴 (유실 불가) |
| 설비 제어 명령 | Request-Reply or Saga |
| 청구/정산 관련 | Outbox (반드시 보장) |
센서 데이터는 fire-and-forget이 맞지만, 알람처럼 놓치면 안 되는 이벤트는 ACK나 Outbox로 분리하는 게 실무 설계입니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 도커티슈박스
- 개발자
- 2017 티스토리 결산
- docker container whale
- docker container
- docker container tissue box
- docker container tissue
- 도커컨테이너
- Linux
- 이직
- docker container case
- vim
- 도커티슈케이스
- 싱가폴
- Sh
- shellscript
- 도커각티슈박스
- 도커각티슈케이스
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
글 보관함
