비동기 프로그래밍
서버 API 호출, 파일 읽기, 데이터베이스 쿼리는 모두 비동기 작업입니다. Dart는 단일 스레드 이벤트 루프 기반으로, Future와 Stream이 비동기의 핵심입니다. 실무 패턴 중심으로 빠르게 복습합니다.
Future
미래에 완료될 단일 값을 나타냅니다. JavaScript의 Promise와 같은 개념입니다.
// Future 반환 함수
Future<String> fetchUser(int id) async {
await Future.delayed(Duration(seconds: 1)); // 네트워크 요청 시뮬레이션
return 'User $id';
}
// Future 직접 생성
final f1 = Future.value(42); // 즉시 완료
final f2 = Future.error('오류 발생'); // 즉시 에러
final f3 = Future.delayed(
Duration(seconds: 2),
() => 'delayed result',
);
async / await
Future를 동기 코드처럼 읽기 쉽게 작성합니다.
Future<void> main() async {
// await로 Future 완료 대기
final user = await fetchUser(1);
print(user); // User 1
// 에러 처리
try {
final data = await riskyOperation();
print(data);
} catch (e) {
print('에러: $e');
} finally {
print('항상 실행');
}
}
Future<String> riskyOperation() async {
throw Exception('Something went wrong');
}
병렬 실행
여러 Future를 순서 없이 동시에 실행합니다.
Future<void> fetchAll() async {
// 순차 실행 (느림): 1초 + 1초 = 2초
final a = await fetchUser(1);
final b = await fetchUser(2);
// 병렬 실행 (빠름): max(1초, 1초) = 1초
final results = await Future.wait([
fetchUser(1),
fetchUser(2),
fetchUser(3),
]);
print(results); // [User 1, User 2, User 3]
// 하나라도 에러나면 전체 실패
// 에러를 개별적으로 처리하려면
final futures = [fetchUser(1), fetchUser(2)];
final settled = await Future.wait(
futures,
eagerError: false, // 에러가 있어도 나머지 완료 대기
);
}
Stream
시간에 따라 여러 값을 내보내는 비동기 시퀀스입니다.
// Stream 생성
Stream<int> countdown(int from) async* {
for (var i = from; i >= 0; i--) {
yield i; // 값 하나씩 내보냄
await Future.delayed(Duration(seconds: 1));
}
}
// Stream 소비
Future<void> main() async {
// await for: Stream 완료까지 대기
await for (final n in countdown(5)) {
print(n); // 5, 4, 3, 2, 1, 0
}
}
StreamController
직접 제어하는 Stream을 만들 때 사용합니다.
import 'dart:async';
final controller = StreamController<String>();
// 값 추가
controller.sink.add('Hello');
controller.sink.add('World');
controller.sink.close();
// 구독
controller.stream.listen(
(data) => print(data),
onError: (e) => print('에러: $e'),
onDone: () => print('완료'),
);
Stream 변환
Stream<int> numbers = Stream.fromIterable([1, 2, 3, 4, 5]);
// map, where, take, skip 등 Iterable과 유사
final result = numbers
.where((n) => n.isOdd)
.map((n) => n * 10);
await for (final n in result) {
print(n); // 10, 30, 50
}
에러 처리
// Future 에러 처리
Future<String> safeOperation() async {
try {
return await riskyOperation();
} on TimeoutException {
return 'timeout';
} on HttpException catch (e) {
return 'http error: ${e.message}';
} catch (e, stack) {
print('예상치 못한 오류: $e\n$stack');
rethrow; // 다시 던지기
}
}
// 타임아웃 설정
final result = await fetchUser(1).timeout(
Duration(seconds: 5),
onTimeout: () => 'timeout',
);
// Stream 에러 처리
Stream<int> numbers = Stream.fromIterable([1, 2, 3]);
numbers.handleError((e) => print('stream error: $e'));
Isolate 개요
Dart는 단일 스레드지만 Isolate로 진정한 병렬 실행이 가능합니다. Isolate 간에는 메모리를 공유하지 않고 메시지를 주고받습니다.
import 'dart:isolate';
// 간단한 Isolate 실행 (Dart 2.19+)
Future<void> main() async {
// compute와 유사한 패턴
final result = await Isolate.run(() {
// 이 블록은 별도 Isolate에서 실행
var sum = 0;
for (var i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
});
print(result);
}
CPU 집약적인 작업(이미지 처리, 암호화, 대용량 파싱)에 Isolate를 활용합니다. 이벤트 루프를 블로킹하지 않기 위해서입니다.
정리
핵심 요점만 정리합니다.
Future는 단일 비동기 값,Stream은 여러 비동기 값의 시퀀스입니다.async/await로 비동기 코드를 동기처럼 읽기 쉽게 작성합니다.Future.wait으로 여러 작업을 병렬로 실행합니다.async*와yield로 커스텀 Stream을 만듭니다.- CPU 집약 작업에는
Isolate.run으로 이벤트 루프 블로킹을 방지합니다.