-
코드팩토리의 플러터 프로그래밍 (책_비동기 프로그래밍)카테고리 없음 2024. 3. 22. 18:09
Future
List나 Set처럼 제네릭으로 어떤 미래의 값을 받아올지를 정할 수 있다.
Future<String> name; // 미래에 받을 String값 Future<int> number; // 미래에 받을 int값 Future<bool> isOpened; // 미래에 받을 boolean값
비동기 프로그래밍은 서버 요청과 같이 오래 걸리는 작업을 기다린 후 값을 받아와야 하기 떄문에 미래값을 표현하는 Future클래스가 필요하다.
void main() { addNumbers(1, 1); } void addNumbers(int number1, int number2){ print('$number1 + $number2 계산 시작!'); // 1 Future.delayed()를 사용하면 일정 시간 후에 콜백 함수를 실행할 수 있음 Future.delayed(Duration(seconds: 3), (){ print('$number1 + $number2 = ${number1 + number2}'); }); print('$number1 + $number2 코드 실행 끝'); } // 실행결과 // 1 + 1 계산 시작! // 1 + 1 코드 실행 끝 // 1 + 1 = 2
첫 번째 매개변수에 대기할 기간을 입력하고 두 번째 매개변수에 대기 후 실해앟ㄹ 콜백 함수를 입력하면 된다.
CPU가 아무것도 하지 않으며 낭비할 뻔한 3초동안 다른 작업을 할 수 있어 더 효율적으로 CPU리소스를 사용한다.
async와 await
코드가 작성된 순서대로 실행되지 않는다면 프로그래머 입장에서 헷갈릴 수 있다.
void main() { addNumbers(1, 1); } // async 키워드는 함수 매개변수 정의와 바디 사이에 입력합니다. Future<void> addNumbers(int number1, int number2) async { print('$number1 + $number2 계산 시작!'); // await는 대기하고 싶은 비동기 함수 앞에 입력합니다. await Future.delayed(Duration(seconds: 3), (){ print('$number1 + $number2 = ${number1 + number2}'); }); print('$number1 + $number2 코드 실행 끝'); } // 실행결과 // 1 + 1 계산 시작! // 1 + 1 = 2 // 1 + 1 코드 실행 끝
이렇게 하면 순서대로 실행되지만 동기 프로그램이 아니냐고 생각할 수 있다.
그치만 어싱크어웨잇 키워드를 사용하면 비동기 프로그래밍 특징을 그대로 유지하면서 순서대로 프로그램을 실행한다.
void main() { addNumbers(1, 1); addNumbers(2, 2); } // 1 + 1 계산 시작! // 2 + 2 계산 시작! // 1 + 1 = 2 // 1 + 1 코드 실행 끝 // 2 + 2 = 4 // 2 + 2 코드 실행 끝
보면addNumbers(1, 1)이 시작되고 끝나기 전에 addNumbers(2, 2)가 실행된다. 그 이유는 addNumbers()함수가 비동기 프로그래밍으로 실행되었기 때문이다.
첫번째를 실행하고 3초를 기다려야 할 떄 CPU의 리소스가 낭비되지 않고 바로 다음 실행할 코드인 두번째애드넘버를 실행한거다.
이로써 비동기로 실행된다는게 증명된다.
void main() async { await addNumbers(1, 1); await addNumbers(2, 2); } // async 키워드는 함수 매개변수 정의와 바디 사이에 입력합니다. Future<void> addNumbers(int number1, int number2) async { print('$number1 + $number2 계산 시작!'); // await는 대기하고 싶은 비동기 함수 앞에 입력합니다. await Future.delayed(Duration(seconds: 3), (){ print('$number1 + $number2 = ${number1 + number2}'); }); print('$number1 + $number2 코드 실행 끝'); } // 실행결과 // 1 + 1 계산 시작! // 1 + 1 = 2 // 1 + 1 코드 실행 끝 // 2 + 2 계산 시작! // 2 + 2 = 4 // 2 + 2 코드 실행 끝
결과값 반환받기
async와 await키워드를 사용한 함수에서도 결과값을 받아낼 수 있고, 이때 앞서 배운 Future클래스를 사용한다.
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 계산 시작! // 1 + 1 = 2 // 1 + 1 코드 실행 끝 // 결괏값 2 // 2 + 2 계산 시작! // 2 + 2 = 4 // 2 + 2 코드 실행 끝 // 결괏값 4
Stream
Future는 반환값을 딱 한 번 받아내는 비동기 프로그래밍에 사용한다.
지속적으로 값을 반환 받을 때는 Stream을 사용한다. 스트림은 한번 리슨하면 스트림에 주입되는 모든 값들을 지속적으로 받아온다.
Future.wait( ) 함수는 하나의 Future로 구성된 리스트를 매개변수로 입력 받는다.
Future.wait( )에 입력된 비동기 함수들은 모두 동시에 실행되며 응답값을 요청을 보낸 순서대로 저장해둔다.
(호출한 순서대로 응답값을 받지는 않는다)
Stream 기본 사용법
스트림을 사용하려면 플러터에서 기본으로 제공하는 dart:async 패키지를 불러와야 한다.
그다음 패키지에서 제공하는 StreamController를 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
브로드캐스트 스트림
스트림은 단 한번만 리슨을 실행할 수 있다. 하지만 떄때로 하나의 스트림을 생성하고 여러 번 리슨 함수를 실행하고 싶을 때가 있다.
이럴 때 브로드캐스트 스트림을 사용하면 여러번 리슨하도록 변환할 수 있다.
import 'dart:async'; void main() { final controller = StreamController(); // 여러 번 리슨할 수 있는 Broadcast Stream 객체 생성 final stream = controller.stream.asBroadcastStream(); // 첫 listen() 함수 final streamListener1 = stream.listen((val) { print('listening 1'); print(val); }); // 두 번째 listen() 함수 final streamListener2 = stream.listen((val) { print('listening 2'); print(val); }); // add()를 실행할 때마다 listen()하는 // 모든 콜백 함수에 값이 주입됩니다. controller.sink.add(1); controller.sink.add(2); controller.sink.add(3); } // 실행결과 // listening 1 // 1 // listening 2 // 1 // listening 1 // 2 // listening 2 // 2 // listening 1 // 3 // listening 2 // 3
함수로 Stream 반환하기
스트림컨트롤러를 직접 사용하지 않고도 직접 스트림을 반환하는 함수를 작성할 수도 있다.
Future를 반환하는 함수는 async로 함수를 선언하고 리턴 키워드로 값을 반환하면되고 스트림을 반환하는 함수는 async로 함수를 선언하고 yield 키워드로 값을 반환해주면 된다.
import 'dart:async'; // Stream을 반환하는 함수는 async*로 선언합니다. Stream<String> calculate(int number) async* { for (int i = 0; i < 5; i++) { // StreamController의 add()처럼 yield 키워드를 이용해서 값 반환 yield 'i = $i'; await Future.delayed(Duration(seconds: 1)); } } void playStream() { // StreamController와 마찬가지로 listen() 함수로 콜백 함수 입력 calculate(1).listen((val) { print(val); }); } void main() { playStream(); } // 실행결과 // i = 0 // i = 1 // i = 2 // i = 3 // i = 4