Dart 비동기 프로그래밍

원종인·2022년 6월 25일
0

Dart 공부

목록 보기
4/4

Future

future는 미래에 받아올 값으로, 지금까지 배운 모든 타입에 적용 가능
.delayed()함수에는 파라미터를 두개 받는데, 하나는 지연할 기간이고 지연 시간이 지난 후 실행할 함수를 넣는다. -> 2초후에 나옴

void main() {
  Future<String> name = Future.value("코드팩토리");
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);

  print("함수 시작");

  Future.delayed(Duration(seconds: 2), () {
    print("딜레이 끝");
  });
}
<결과>
함수 시작
딜레이 끝

Dart는 asynchronous programming을 기반으로 하기에 아래와 같은 결과가 나온다. 딜레이가 끝나고 다음 결과가 나오는 것이 아닌, 딜레이가 되면서 다음이 진행된다.

void main() {
  Future<String> name = Future.value("코드팩토리");
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);

  addNumbers(1,1);
}

void addNumbers(int number1, int number2){
  print("계산시작: $number1 + $number2");
  
  Future.delayed(Duration(seconds:2),(){
    print("계산 완료: $number1 + $number2 = ${number1 + number2}");
  });
  
  print("함수완료");
}
<결과>
계산시작: 1 + 1
함수완료
계산 완료: 1 + 1 = 2

함수를 하나 실행하든 두개 실행하든 asynchronous programming은 이렇게 작동한다.

void main() {
  Future<String> name = Future.value("코드팩토리");
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);

  addNumbers(1,1);
  addNumbers(2,2);
}

void addNumbers(int number1, int number2){
  print("계산시작: $number1 + $number2");
  
  Future.delayed(Duration(seconds:2),(){
    print("계산 완료: $number1 + $number2 = ${number1 + number2}");
  });
  
  print("함수완료");
}
<결과>
계산시작: 1 + 1
함수완료
계산시작: 2 + 2
함수완료
계산 완료: 1 + 1 = 2
계산 완료: 2 + 2 = 4

await

await를 쓰려면 파라미터()와 바디{} 사이에 async를 넣어주어야 한다.
그러면 Future관련된 async 함수에 await를 넣어줄 수 있다.
해당 방식은 이전의 방식들도 좋긴 하지만 내가 서버를 통해서 값을 받은 다음 해당 값을 다음 함수에 넣어주거나 등 이전의 상황이 먼저 선행이 완료되어야 하는 경우 await를 사용해준다.

void main() {
  Future<String> name = Future.value("코드팩토리");
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);

  addNumbers(1, 1);
}

void addNumbers(int number1, int number2) async {
  print("계산시작: $number1 + $number2");

  await Future.delayed(Duration(seconds: 2), () {
    print("계산 완료: $number1 + $number2 = ${number1 + number2}");
  });

  print("함수완료");
}
<결과>
계산시작: 1 + 1
계산 완료: 1 + 1 = 2
함수완료

그렇다고 CPU가 무조건 await하느라고 기다리는 것은 아니다.
1+1, 2+2처럼 기다리는 동안 다른 작업을 할 수 있다.

void main() {
  Future<String> name = Future.value("코드팩토리");
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);

  addNumbers(1, 1);
  addNumbers(2, 2);
}

void addNumbers(int number1, int number2) async {
  print("계산시작: $number1 + $number2");

  await Future.delayed(Duration(seconds: 2), () {
    print("계산 완료: $number1 + $number2 = ${number1 + number2}");
  });

  print("함수완료");
}
<결과>
계산시작: 1 + 1
계산시작: 2 + 2
계산 완료: 1 + 1 = 2
함수완료
계산 완료: 2 + 2 = 4
함수완료

그런데 여기서 addNumbers까지 하나 실행하고 기다렸다가 실행하고 싶다면 main도 유사하게 사용한다.
async넣어주고 await만 넣어주면 오류가 난다. 그런데 await는 Future를 리턴해야 하는데 아래 함수는 void를 반환하기에 오류가 생기는 것. 그렇기에 함수를 Future로 만들고 void를 감싸면 된다.

void main() async {
  Future<String> name = Future.value("코드팩토리");
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);

  await addNumbers(1, 1);
  await addNumbers(2, 2);
}

Future<void> addNumbers(int number1, int number2) async {
  print("계산시작: $number1 + $number2");

  await Future.delayed(Duration(seconds: 2), () {
    print("계산 완료: $number1 + $number2 = ${number1 + number2}");
  });

  print("함수완료");
}
<결과>
계산시작: 1 + 1
계산 완료: 1 + 1 = 2
함수완료
계산시작: 2 + 2
계산 완료: 2 + 2 = 4
함수완료

Returning Future

그러면 void가 아닌 int나 다른 변수형들은 어떻게 사용할까?
int로 만들어주고 return 해주면 된다. 그럼 Future가 알아서 Future로 감싸서 return 해준다.

void main() async {
  Future<String> name = Future.value("코드팩토리");
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);

  final result1 = await addNumbers(1, 1);
  final result2 = await addNumbers(2, 2);
  
  print("result1 : $result1");
  print("result2 : $result2");
  print("result1 + result2 = ${result1+result2}");
}

Future<int> addNumbers(int number1, int number2) async {
  print("계산시작: $number1 + $number2");

  await Future.delayed(Duration(seconds: 2), () {
    print("계산 완료: $number1 + $number2 = ${number1 + number2}");
  });

  print("함수완료");
  
  return number1 + number2;
}
<결과>
계산시작: 1 + 1
계산 완료: 1 + 1 = 2
함수완료
계산시작: 2 + 2
계산 완료: 2 + 2 = 4
함수완료
result1 : 2
result2 : 4
result1 + result2 = 6

Stream 기본기

Dart언어에서 기본적으로 제공하는 기능이 아니기에 import해와야 한다.
StreamController()를 불러오면 stream을 부를 수 있다.
리스닝을 통해서 리스너에게 add를 통해서 값을 전달할 수 있다. 그리고 여러개의 값을 전달할 수도 있다.

import "dart:async";

void main(){
  final controller = StreamController();
  final stream = controller.stream;
  
  final streamListener1 = stream.listen((val){
    print("Listener 1 : $val");
  });
  
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
}
<결과>
Listener 1 : 1
Listener 1 : 2
Listener 1 : 3
Listener 1 : 4
Listener 1 : 5

스트림을 여러번 리스닝하는 방법, 리스닝을 여러번 선언하기
스트림을 선언하면 기본적으로 한번만 리스닝을 할 리소스가 생긴다. 이를 보완하기 위해서 .asBroadcastStream()을 사용해 여러번 리스닝을 할 스트림을 만든다.

import "dart:async";

void main() {
  final controller = StreamController();
  final stream = controller.stream.asBroadcastStream();

  final streamListener1 = stream.listen((val) {
    print("Listener 1 : $val");
  });
  
  final streamListener2 = stream.listen((val) {
    print("Listener 2 : $val");
  });

  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
}
<결과>
Listener 1 : 1
Listener 2 : 1
Listener 1 : 2
Listener 2 : 2
Listener 1 : 3
Listener 2 : 3
Listener 1 : 4
Listener 2 : 4
Listener 1 : 5
Listener 2 : 5

그러면 즉석에서 스트림값을 변경하는 방법을 알아보자
함수형 기능을 여기서도 사용할 수 있다.
.where()를 통해서 조건을 설정할 수 있다.

import "dart:async";

void main() {
  final controller = StreamController();
  final stream = controller.stream.asBroadcastStream();

  final streamListener1 = stream.where((val) => val % 2 == 0).listen((val) {
    print("Listener 1 : $val");
  });

  final streamListener2 = stream.where((val) => val % 2 == 1).listen((val) {
    print("Listener 2 : $val");
  });

  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
}
<결과>
Listener 2 : 1
Listener 1 : 2
Listener 2 : 3
Listener 1 : 4
Listener 2 : 5

Stream 함수

함수는 return을 만나면 해당 값을 반환하고 종료한다.
그렇지만 Stream 함수는 이를 해결한다.
우선 함수를 Stream<>으로 만들어준다. 그리고 파라미터와 바디 사이에 async*을 넣어준다. 그리고 return값 대신 yield를 사용하면 된다.

import "dart:async";

void main() {
  calculate(2).listen((val){
    print("calculate(2) : $val");
  });
}

Stream<int> calculate(int number) async* {
  for(int i = 0; i < 5; i++) {
    yield i * number;
  }
}
<결과>
calculate(2) : 0
calculate(2) : 2
calculate(2) : 4
calculate(2) : 6
calculate(2) : 8

Stream에서의 async 프로그래밍

async*을 선언해도 await같은 async를 사용하는 언어도 사용가능하다.
그리고 해당 기능에서 await를 사용한다고 cpu가 노는 것이 아닌 일은 계속 진행한다.

import "dart:async";

void main() {
  calculate(2).listen((val){
    print("calculate(2) : $val");
  });
  
  calculate(4).listen((val){
    print("calculate(4) : $val");
  });
}

Stream<int> calculate(int number) async* {
  for(int i = 0; i < 5; i++) {
    yield i * number;
  }
  
  await Future.delayed(Duration(seconds : 1));
}
<결과>
calculate(2) : 0
calculate(4) : 0
calculate(2) : 2
calculate(4) : 4
calculate(2) : 4
calculate(4) : 8
calculate(2) : 6
calculate(4) : 12
calculate(2) : 8
calculate(4) : 16

Yield

만약 우리가 calculate2가 끝난다음 calculate4를 진행시키고 싶으면 어떡하고 싶은가, 즉 Stream을 await하고 싶으면 어떻게 하는가?
yield를 하면 값을 하나하나 순서대로 가져오지만 yield*을 하면 해당되는 스트림의 모든 값을 가져올 때까지 기다린다. 즉 다 return되서 함수의 Stream이 끝나야지만 다은 Stream이 진행된다.

import "dart:async";

void main() {
  playAllStream().listen((val){
    print(val);
  });
}

Stream<int> playAllStream() async* {
  yield* calculate(1);
  yield* calculate(1000);
}

Stream<int> calculate(int number) async* {
  for(int i = 0; i < 5; i++) {
    yield i * number;
  }
  
  await Future.delayed(Duration(seconds : 1));
}
<결과>
0
1
2
3
4
0
1000
2000
3000
4000

근데 기본적으로 더 좋은 라이브러리 사용해서 async 안 쓰긴하는데 알아서 나쁠것 없다.

복습 끝!!!!!!!!!!!

profile
아직 대학생입니다

0개의 댓글