Study Record

[Dart] 비동기 프로그래밍 본문

Dart

[Dart] 비동기 프로그래밍

초코초코초코 2023. 1. 16. 23:22
728x90

비동기 프로그래밍?

비동기 프로그래밍(비동기 처리)은 현재 실행 중인 것이 완료되지 않더라도 다음 코드를 실행하는 방식을 말한다.

 

일반적으로 코드를 실행하면 위에서부터 아래로 순차적으로 실행한다. 중간에 시간이 오래 걸리거나 대기시간이 있는 작업이라고 해도 순서대로 실행하게 된다. 코드 순서가 A ▶ B ▶ C 인 프로그램이 있다고 하자. 이 프로그램의 B 과정이 중간에 대기시간이 있다고 하면 일반적인 동기 프로그래밍은 대기시간이 있더라도 대기하며 순차적으로 A ▶ B ▶ C 를 실행하게 될 것이다. 

 

C 과정이 A와 B에 직접적인 관련이 없는 작업이라면 대기시간 동안 C 과정을 일부 혹은 전부 실행하고 나머지 B 과정을 처리하는 게 효율적일 것이다. 이것을 가능하게 하는 것이 바로 비동기 프로그래밍이다.

 

✍ Future

비동기 프로그래밍에 자주 쓰이는 클래스이다.

 

※ Future.delayed() 함수

Future<Null> Future.delayed(Duration duration, [FutureOr<Null> Function()? computation])
- duration : 지연시간
- computation : 지연시간 이후 실행될 함수
duration 대기 시간 후 computation 함수가 실행된다.

 

😶 사용 예시

void main() {
    addNumbers(2, 5);
}

void addNumbers(int n1, int n2){
    print("함수 시작 : $n1 , $n2");

    // 3초 뒤 다음 함수 실행
    Future.delayed(Duration(seconds: 3), (){
        print("계산 끝 : $n1 + $n2 = ${n1 + n2}");;
    });

    print("함수 끝 : $n1 , $n2");
}

/* 실행 결과값
 * 함수 시작 : 2 , 5
 * 함수 끝 : 2 , 5
 * 계산 끝 : 2 + 5 = 7
 */

 

 

😶 async +  await 키워드

async 표시된 함수에 await 키워드가 붙은 부분은 그 부분이 끝날 때까지 기다린다. await 키워드는 반드시 Future 클래스를 반환하는 함수 앞에만 붙일 수 있다. 기다린다는 의미가 프로그램 실행 중 대기상태가 되는 것이 아니라 async로 실행하는 함수 안에서 await 하라는 의미이기 때문에 실행할 수 있는 다른 작업이 있으면 실행한다.

void main() {
    addNumbers(2, 5);
    addNumbers(4, 6);
}

void addNumbers(int n1, int n2) async {
    print("함수 시작 : $n1 , $n2");

    await Future.delayed(Duration(seconds: 3), (){
        print("계산 끝 : $n1 + $n2 = ${n1 + n2}");;
    });

    print("함수 끝 : $n1 , $n2");
}

/* 실행 결과값
 * 함수 시작 : 2 , 5
 * 함수 시작 : 4 , 6
 * 계산 끝 : 2 + 5 = 7
 * 함수 끝 : 2 , 5
 * 계산 끝 : 4 + 6 = 10
 * 함수 끝 : 4 , 6
 */

위의 예시에서도 async 로 실행되는 addNumber() 함수에서 Future.delayed() 앞에 await 키워드가 있기 때문에 처음 "함수 시작 : 2 , 5" 가 실행된 후 await로 기다리는 동안 "함수 시작 : 4 , 6"부분을 실행하고 있는 것을 볼 수 있다.

 

 

😶 리턴값이 있는 async 함수

리턴값이 있는 async 함수를 만들려면 리턴값 타입을 Future <T>로 설정하면 된다. T에 원하는 데이터 타입을 넣어주고 그 데이터 타입을 가진 값을 return 해주면 된다.

void main() async {
    final result1 = await addNumbers(2, 5);
    final result2 = await addNumbers(4, 6);
    
    print("$result1 + $result2 = ${result1 + result2}");
}

Future<int> addNumbers(int n1, int n2) async {
    await Future.delayed(Duration(seconds: 1), (){
        print("계산 끝 : $n1 + $n2 = ${n1 + n2}");;
    });
    
    return n1 + n2;
}

/* 실행 결과값
 * 계산 끝 : 2 + 5 = 7
 * 계산 끝 : 4 + 6 = 10
 * 7 + 10 = 17 
 */

 

 

😶 주의해야할 점

int result = await fetchData();

Future<int> result = fetchData();

fetchData() 가 비동기 함수이고 int 값을 리턴한다고 했을 때, await 키워드가 붙은 "await fetchData()" 는 fetchData() 라는 비동기 함수가 다 작업을 마칠때까지 기다린 후 리턴값(int)을 result 에 대입하는 방향으로 흘러간다.

await 키워드가 붙지 않은 "fetchData()" 는 이미 작업이 끝나지 않았기 때문에 "int result" 로는 대입할 수 없고 Future<int> 로 받아야한다. 

 

 

  Stream

Future 는 한 함수에서 하나의 값만 받아낼 수 있다. 여러 개의 값을 받아내기 위해서는 List 등을 사용하는 방법밖에 없다. 이런 점을 개선하기 위해 Stream이라는 개념이 나왔다.

 

Stream 은 완료하기 전까지 반환값(yield)을 계속 받을 수 있다.

 

😶 기본 사용법

stream 을 사용하기 위해서는 import 'dart:async'; 를 포함해야 한다.

import 'dart:async';

void main(){
  final controller = StreamController();
  final stream = controller.stream;
  
  // 리스너 등록
  final streamListener1 = stream.listen((value){
    print('Listener 1 : $value');
  });
  
  controller.sink.add(1);
  controller.sink.add(3);
  controller.sink.add(5);
  
  // Stream 종료
  controller.close();
  
  // Uncaught Error: Bad state: Cannot add event after closing
  // controller.sink.add(9);
}

/** 실행 결과값
 * Listener 1 : 1
 * Listener 1 : 3
 * Listener 1 : 5
 */

stream에 listener를 등록하고 완료하기 전까지(controller.close()) sink.add() 함수로 listener에 등록한 함수를 실행할 수 있다.

 

 

😶 여러개의 listener 등록 

asBroadcastStream()을 사용한다.

import 'dart:async';

void main(){
  final controller = StreamController();
  final stream = controller.stream.asBroadcastStream();
  
  // 리스너 등록
  final streamListener1 = stream.listen((value){
    print('Listener 1 : $value');
  });
  
  // 리스너 등록
  final streamListener2 = stream.listen((value){
    print('Listener 2 : $value');
  });
  
  controller.sink.add(1);
  controller.sink.add(3);
  controller.sink.add(5);
  
  // Stream 종료
  controller.close();
}

/** 실행 결과값
 * Listener 1 : 1
 * Listener 2 : 1
 * Listener 1 : 3
 * Listener 2 : 3
 * Listener 1 : 5
 * Listener 2 : 5
 */

 

 

😶 조건이 있는 listener 등록

where(조건)을 활용한다.

import 'dart:async';

void main(){
  final controller = StreamController();
  final stream = controller.stream.asBroadcastStream();
  
  // 짝수만 받는 리스너 등록
  final streamListener1 = stream.where((val) => val % 2 == 0).listen((value){
    print('Listener 1 : $value');
  });
  
  // 홀수만 받는 리스너 등록
  final streamListener2 = stream.where((val) => val % 2 == 1).listen((value){
    print('Listener 2 : $value');
  });
  
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
  
  // Stream 종료
  controller.close();
}

/** 실행 결과값
 * 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');
  });
}

Stream<int> calculate(int number) async* {
  for(int i=0; i<3; i++){
      yield i * number;
  }
}

비동기로 실행할 함수에 async* 키워드를 작성해 주고 yield 키워드로 리턴값을 설정하면, 비동기로 실행할 함수(calculate())에 listener를 등록하면 yield로 리턴 받은 값을 캐치한다.

 

 

😶 async* +  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<3; i++){
      yield i * number;
      
      await Future.delayed(Duration(seconds: 2));
  }
}

Future 와 마찬가지로 비동기로 calculate()가 실행되면서 await 부분의 작업이 완료된 후 다음 작업으로 넘어간다. 따라서 첫 번째 for loop에서 리턴값(0)을 리턴한 후 2초 대기 후 두 번째 for loop으로 넘어간다.

 

 

😶 yield*

비동기 함수를 순차적으로 실행하고 싶다면 yield* 키워드를 사용하면 된다. 

import 'dart:async';

void main(){
  playAllStream().listen((val) {
    print('playAllstream() : $val');
  });
}

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

Stream<int> calculate(int number) async* {
  for(int i=1; i<4; i++){
    yield i * number;
    
    await Future.delayed(Duration(seconds: 1));
  } 
}

/** 실행 결과값
 * playAllstream() : 1
 * playAllstream() : 2
 * playAllstream() : 3
 * playAllstream() : 100
 * playAllstream() : 200
 * playAllstream() : 300
 */

PlayAllStream() 함수에서 yield* calculate(1); 이 전부 실행된 후 yield* calculate(100); 이 실행되는 모습을 볼 수 있다.

728x90

'Dart' 카테고리의 다른 글

[Dart] ..  (0) 2023.03.09
[Dart] 난수 생성하기  (0) 2023.02.05
[Dart] 함수형 프로그래밍  (0) 2023.01.15
[Dart] 객체 지향 프로그래밍  (1) 2023.01.12
[Dart] Dart 기본  (0) 2023.01.10