Dart – Async programming

기존 호출 플로우

void main(){
  addNumbers(1,1);
  addNumbers(2,2);
  
  //무조건 처음 호출된 함수가 끝난 후에 다음 함수가 실행됨
}


void addNumbers(int num1,int num2){
  print('calculating.... $num1 + $num2');
  print('calculated ! ${num1 + num2}');
}

Future

void main(){
  // Future - 미래 (비동기 프로그래밍)
  // 미래에 받아올 값
  // Future를 사용하면 cpu가 기다리지 않고 다른 작업을 할 수 있게 처리할 수 있음(낭비를 줄임)
  
  Future<String> name = Future.value('Dylan');
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);
  
  addNumbers(1,1);
  addNumbers(2,2);
}

void addNumbers(int number1, int number2){
   /*
  * delayed - 2개의 파라미터를 받음
  * 1번 param- 지연할 기간
  * 2번 param- 콜백 함수
  */
  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를 쓴다고해서 cpu가 아무것도 안하고 기다리지 않고, 다른 태스크들을 처리하고 데이터가 패칭되었을때 다시 다음 줄을 실행한다. (비동기)

void main(){
  Future<String> name = Future.value('Dylan');
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);
  
  addNumbers(1,1);
  addNumbers(2,2);
}

/*
 * 순서가 중요할 경우는 async - await를 쓴다.
 * */

void addNumbers(int number1, int number2) async {

  print('계산 시작 : $number1 + $number2'); 
  
  //서버 시뮬레이션
  await Future.delayed(Duration(seconds:2), (){
    print('계산 완료: $number1 + $number2 = ${number1 + number2}');
  }); // 데이터를 패칭할때까지 기다림
  
  print('함수 완료 $number1 + $number2 '); 
  /*
    계산 시작 : 1 + 1
    계산 시작 : 2 + 2
    계산 완료: 1 + 1 = 2
    함수 완료 1 + 1 
    계산 완료: 2 + 2 = 4
    함수 완료 2 + 2 
   */
}

main() 쓰레드에서도 실행순서가 중요할경우 이렇게 쓸 수 있음.

void main() async {
  Future<String> name = Future.value('Dylan');
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);
  
  await addNumbers(1,1); //await는 리턴값이 Future여야 사용가능
  await addNumbers(2,2);
  
  /*
   * 계산 시작 : 1 + 1
    계산 완료: 1 + 1 = 2
    함수 완료 1 + 1 
    계산 시작 : 2 + 2
    계산 완료: 2 + 2 = 4
    함수 완료 2 + 2 
   * */
}

Future<void> addNumbers(int number1, int number2) async { // return 타입을 Future<void>로 변경

  print('계산 시작 : $number1 + $number2'); 
  
  //서버 시뮬레이션
  await Future.delayed(Duration(seconds:2), (){
    print('계산 완료: $number1 + $number2 = ${number1 + number2}');
  }); 
  
  print('함수 완료 $number1 + $number2 ');
  
}

async 함수 비동기 return

void main() async {
  Future<String> name = Future.value('Dylan');
  Future<int> number = Future.value(1);
  Future<bool> isTrue = Future.value(true);
  
  final result = await addNumbers(1,1); //await는 리턴값이 Future여야 사용가능
  final result2 = await addNumbers(2,2);

  print('result1 : $result');
  print('result2: $result2');
  print('result1 + result2 = ${result + result2}');
}

Future<int> addNumbers(int number1, int number2) async { // return 타입을 Future<void>로 변경

  print('계산 시작 : $number1 + $number2'); 
  
  //서버 시뮬레이션
  await Future.delayed(Duration(seconds:2), (){
    print('계산 완료: $number1 + $number2 = ${number1 + number2}');
  }); 
  
  print('함수 완료 $number1 + $number2 ');
  
  return number1 + number2;
  
}

Stream

Future는 await 를 이용하여 값이 다 불러올때까지 기다리지만, Stream은 완료되는 순간까지 지속적으로 값을 불러올 수 있다.

import 'dart:async';

void main() {
  final controller = StreamController();
//   final stream = controller.stream; // 한개만 listen
  final stream =
      controller.stream.asBroadcastStream(); // 여러번 listening 할 수 있는 stream 생성

  // stream에 값이 들어왔을때 콜백함수 실행
  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); // data input
  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 제공

import 'dart:async';

void main() {
  calculate(1).listen((val){
    print('calculate(1) : $val');
  });
  
  /*
   * 결과값
   * calculate(1) : 0
    calculate(1) : 1
    calculate(1) : 2
    calculate(1) : 3
    calculate(1) : 4
   * */
}

// calculate(int number) {
//   for (int i = 0; i < 5; i++) {
//     return i * number; // return을 하면 함수가 끝나버리기때문에, 맨 처음값만 돌려줄수있음
//   }
// }

// Stream return type을 사용하여 listen 하고있는 곳에 값을 뿌려줄 수있음.
Stream<int> calculate(int number) async*{
  for (int i = 0; i < 5; i++) {
    yield i * number; // yield 는 값을 하나하나 보내줄때 사용됨
  }
}

리턴 타입이 Stream 일때도 await 사용가능

import 'dart:async';

void main() {
  calculate(1).listen((val){
    print('calculate(1) : $val');
  });
  
}

Stream<int> calculate(int number) async*{
  for (int i = 0; i < 5; i++) {
    yield i * number; // yield 는 값을 하나하나 보내줄때 사용됨
  
    await Future.delayed(Duration(seconds:1)); //async* 여도 await를 쓸 수 있음, 1초마다 데이터 전송
  }
}

비동기 증명

import 'dart:async';

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

  calculate(4).listen((val){
    print('calculate(4) : $val');
  });
  
  
  /*
   * 끝날때까지 기다리지않고 calculate(4)가 실행됨 >> 비동기 
   * 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
   * */

}

Stream<int> calculate(int number) async*{
  for (int i = 0; i < 5; i++) {
    yield i * number; // return을 하면 함수가 끝나버리기때문에, 맨 처음값만 돌려줄수있음
  
    await Future.delayed(Duration(seconds:1)); //async* 여도 await를 쓸 수 있음, 1초마다 데이터 전송
  }
}

만약 위에서 calculate(2)가 전부 실행되고 난다음, calculate(4)가 실행되게 하려면 어떻게 해야할까? (Future의 await 기능)

새로운 순서를 조정해주는 함수를 만든 후, yield* 를 사용하여 실행 순서를 보장한다.

import 'dart:async';

void main() async {
  playAllStream().listen((val){
    print(val);
  });
  
  /* 결과값
   * 0
    1
    2
    3
    4
    0
    1000
    2000
    3000
    4000
   * */
}

Stream<int> playAllStream() async*{
  yield* calculate(1); // yield* 은 stream에 모든 값을 다 가져올때까지 기다림 (Future의 await와 비슷함)
  yield* calculate(1000);
}

Stream<int> calculate(int number) async*{
  for (int i = 0; i < 5; i++) {
    yield i * number; // yield 는 값을 하나하나 보내줄때 사용됨
  
    await Future.delayed(Duration(seconds:1)); //async* 여도 await를 쓸 수 있음, 1초마다 데이터 전송
  }
}