코드 팩토리의 플러터 프로그래밍 - 다트 언어 마스터하기, 3장 다트(Dart) 비동기 프로그래밍
"이 글은 골든래빗 《코드 팩토리의 플러터 프로그래밍》의 03장 써머리입니다."
동기 vs 비동기 프로그래밍
- 비동기 프로그래밍은 요청한 결과를 기다리지 않아서 순서대로 실행된다는 보장이 없다.
- 일단 요청을 보내놓고 기다린다.
- UI 프로그래밍에서는 UI 가 블록되지 않도록 하는데 중요한 역할을 한다.
- 동기 프로그래밍만 사용해야 한다면, 시간이 걸리는 작업에서 UI의 반응이 멈춰버린다.
- 네트워크 API 응답을 받거나, 큰 계산, 인코딩 등이 긴 작업에 해당한다.
Future
- 비동기 프로그래밍에 쓰이는
Future
클래스는 이름처럼 미래에 받아올 값을 의미한다.
Future<String> name;
Future<int> number;
Future<bool> isOpen;
Future.delayed()
- 주어진 시간동안 아무것도 하지 않고 기다린다.
- 아래 코드에서 중간에 위치한
1 + 1 = 2
가 가장 나중에 나온다. - 위에서 설명한 비동기 프로그래밍의 특성으로 순서대로 실행된다는 보장이 없다.
void main() {
addNumbers(1, 1);
}
void addNumbers(int number1, int number2) {
print('$number1 + $number2 계산 시작');
Future.delayed(Duration(seconds: 3), () {
print('$number1 + $number2 = ${number1 + number2}');
});
print('$number1 + $number2 코드 실행 끝');
}
// 출력 결과:
/*
1 + 1 계산 시작
1 + 1 코드 실행 끝
1 + 1 = 2
*/
async
와 await
- 비동기 프로그래밍을 순서대로 실행시키는 방법이다.
- 인간은 순서대로 실행되는 게 보장되어야 생각하기 편하다.
- 함수 파라미터 끝에
async
가 붙었고,Future.delayed
앞에await
이 붙었다.
void main() {
addNumbers(1, 1);
}
void addNumbers(int number1, int number2) async {
print('$number1 + $number2 계산 시작');
await Future.delayed(Duration(seconds: 3), () {
print('$number1 + $number2 = ${number1 + number2}');
});
print('$number1 + $number2 코드 실행 끝');
}
// 출력 결과
/*
1 + 1 계산 시작
1 + 1 = 2
1 + 1 코드 실행 끝
*/
async
, await
을 써도 여전히 비동기
async
,await
을 쓰면 동기와 무슨 차이냐고 생각할 수 있지만, 여전히 비동기이다.async
함수 영역 안에서만 동기처럼 실행되는 것이고, 밖에선 아니다.addNumbers()
함수의 관점에서는 동기처럼 실행된다.main()
함수의 관점에서는 비동기처럼 실행된다.
- 여전히 CPU 리소스의 낭비를 막을 수 있다.
main()
자체도async
,await
으로 만들면 한 계층 밖으로 비동기 동작방식을 또 동기로 꺼내올 수 있다.
void main() {
addNumbers(1, 1);
addNumbers(2, 2);
}
Future<void> addNumbers(int number1, int number2) async {
print('$number1 + $number2 계산 시작');
await Future.delayed(Duration(seconds: 3), () {
print('$number1 + $number2 = ${number1 + number2}');
});
print('$number1 + $number2 코드 실행 끝');
}
// 출력 결과
/*
1 + 1 계산 시작
2 + 2 계산 시작
1 + 1 = 2
1 + 1 코드 실행 끝
2 + 2 = 4
2 + 2 코드 실행 끝
*/
결과값 반환받기
void main() async {
final result = await addNumbers(1, 1);
print('결괏값 $result');
final result2 = await addNumbers(2, 2);
print('결괏값 $result2');
}
Future<int> addNumbers(int number1, int number2) async {
print('$number1 + $number2 계산 시작!');
await Future.delayed(Duration(seconds: 3), () {
print('$number1 + $number2 = ${number1 + number2}');
});
print('$number1 + $number2 코드 실행 끝');
return number1 + number2;
}
// 출력 결과
/*
1 + 1 계산 시작!
... 3초 딜레이 ...
1 + 1 = 2
1 + 1 코드 실행 끝
결괏값 2
2 + 2 계산 시작!
... 3초 딜레이 ...
2 + 2 = 4
2 + 2 코드 실행 끝
결괏값 4
*/
Stream
Future
는 값을 딱 한번 반환받는 경우- 지속적으로 반환받는다면
Stream
을 사용 Stream
은 한번listen
하면Stream
에 주입되는 모든 값들을 지속적으로 받아옴- 다만
for
,await
도 데이터가 한번 이상 반환되는 경우에 적합하므로, 어떤 것이 효율적인지 따져봐야 한다.
Future.wait()
함수는Future
의 리스트를 매개변수로 받아 비동기 작업을 동시에 수행하고 응답값을 요청 보낸 순서대로 저장해둔다.
Stream
기본 사용법
dart:async
패키지를 불러와야 한다.StreamController
내부의stream
을listen()
해야 한다.
import 'dart:async';
void main() {
final controller = StreamController(); // StreamController 선언
final stream = controller.stream; // Stream 가져오기
// Stream 에 listen() 함수를 실행하면 값이 주입될 때마다 콜백 함수를 실행
final streamListener1 = stream.listen((val) {
print(val);
});
// stream 에 값 주입
controller.sink.add(1);
controller.sink.add(2);
controller.sink.add(3);
controller.sink.add(4);
}
// 출력 결과
/*
1
2
3
4
*/
브로드캐스트 스트림
- 하나의 스트림에 리스너를 여러개 등록하고 싶을 때 사용한다.
- 브로드캐스트 스트림이 아니라면, 두번째 리스너 등록에
Uncaught Error: Bad state: Stream has already been listened to.
에러가 표출된다. - 아래 코드의 경우,
sink
에add()
메서드가 한번 일어날 때마다 두개의 리스너가 반응한다. - JS 의 경우
addEventListener(event, () {})
로 이벤트를 등록하면 이벤트를 여러개 등록할 수 있어 브로드캐스트 스트림과 흡사하게 된다.- 다만
.onclick = () => {}
와 같은 방식으로 등록하면 이벤트가 최대 1개만 등록된다.
- 다만
import 'dart:async';
void main() {
final controller = StreamController();
final stream = controller.stream.asBroadcastStream(); // BroadcastStream 가져오기
final streamListenerA = stream.listen((val) {
print('listener A $val');
});
final streamListenerB = stream.listen((val) {
print('listener B $val');
});
controller.sink.add(1);
controller.sink.add(2);
controller.sink.add(3);
}
// 출력 결과
/*
listener A 1
listener B 1
listener A 2
listener B 2
listener A 3
listener B 3
*/
함수로 Stream
반환하기
StreamController
를 사용하지 않고Stream
을 반환하는 함수를 작성할 수 있다.- 반환 타입을
Stream
으로 설정하고,async*
로 함수를 선언하고,yield
키워드로 값을 반환하면 된다. - 아래의
calculate()
는Stream
을numberOfStream
의 크기만큼 반환한다.- 반환된 모든
Stream
에listen()
메서드를 적용시킨다.
- 반환된 모든
import 'dart:async';
Stream<String> calculate(int numberOfStream) async* {
for (int i=0; i<numberOfStream; i++) {
// controller.sink.add() 를 이용했던 것처럼 yield 를 이용해 값 반환
yield 'i = ${i+1}';
await Future.delayed(Duration(seconds: 1));
}
}
void playStream() {
calculate(3).listen((val) {
print(val);
});
}
void main() {
playStream();
}
// 출력 결과
/*
i = 1
i = 2
i = 3
*/
요약
- 비동기 프로그래밍은 UI 에서 블록이 일어나지 않도록 리소스가 막히는 상황을 방지하는데 유용
Future
: 비동기로 '한번' 값을 받을 때 유용async await
: 비동기 작업을 함수 내에서 순차적으로 실행시킬 때 유용Stream
:listen()
을 통해 비동기 응답 이벤트가 발생할 때마다 지속적으로 동작할 콜백 함수 정의 가능async*
과yield
를 이용한 함수로Stream
타입을 반환할 수도 있다.
반응형
'코드팩토리의 플러터 프로그래밍' 카테고리의 다른 글
코드 팩토리의 플러터 프로그래밍 - 플러터 기본 다지기, 4장 플러터 입문하기 (1) | 2023.10.29 |
---|---|
코드 팩토리의 플러터 프로그래밍 - 다트 언어 마스터하기, 2장 다트(Dart) 객체지향 프로그래밍 (1) | 2023.10.15 |
코드 팩토리의 플러터 프로그래밍 - 다트 언어 마스터하기, 1장 다트 (Dart) 입문하기 (0) | 2023.10.08 |