비동기
포스트
취소

비동기

동기 프로그래밍과 비동기 프로그래밍

  • 동기 프로그래밍
    • 함수를 실행하면 다음 코드가 실행되기 전에 해당 함수의 결과 값이 먼저 반환
    • 예시
      • 요청1 → 응답1 → 요청2 → 응답2 → 요청3 → 응답3
  • 비동기 프로그래밍
    • 요청한 결과를 기다리지 않으며 응답 순서 또한 요청한 순서와 다를 수 있다.
    • 예시
      • 요청1 → 요청2 → 응답1 → 응답2 → 요청3 → 응답3

Future

  • 시간이 오래걸리는 작업을 기다린 후 결과값을 받아와야하는
    비동기 프로그래밍을 위해 만들어진 제네릭 클래스
  • 미래 어느 시점에서 사용할 수 있는 잠재적인 값 또는 에러
  • 최종 결과 값을 반환한다.
void main() {
  futureExample();
}

void futureExample(){
  print("메소드 시작!!!");
  Future.delayed(const Duration(seconds: 3), (){
    print("비동기 실행!!!");
  });
  print("메소드 종료!!!");
}

출력 결과

메소드 시작!!!
메소드 종료!!!
비동기 실행!!!

async와 await

async

  • await 처리한 비동기 메소드를 실행하기 위해 필요한 키워드
  • 비동기 메소드가 실행될 메소드의 파라미터와 몸체 사이에 선언한다.
  • 내부에 await 키워드가 존재하지 않아도 사용할 수 있다.
  • 해당 키워드가 있어야지 Future를 반환형으로 사용할 수 있다.

await

  • 비동기 메소드를 동기 처리 하기 위한 키워드
  • 동기 처리할 비동기 메소드 앞에 선언한다.
  • Future 타입에만 사용할 수 있다.
  • Future 타입의 값을 T 타입으로 반환한다.

예시 (async와 await)

void main() async {
  await futureExample();
  var result = await futureExample2();
  print(result.runtimeType);
}

Future<void> futureExample() async {
  print("메소드 시작!!!");
  await Future.delayed(const Duration(seconds: 3), (){
    print("비동기 실행!!!");
  });
  print("메소드 종료!!!");
}

Future<int> futureExample2() async {
  return 3;
}

출력 결과

메소드 시작!!!
비동기 실행!!!
메소드 종료!!!
int

Future 관련 메소드

then

  • Future 객체를 반환하는 메소드의 결과에 대해서 처리하는 메소드
  • Future 객체의 제네럴 타입이 반환되는 값의 자료형이 된다.
void main() {
  futureExample().then((value){
    print(value.runtimeType); //출력 : int
  });
}

Future<int> futureExample() async {
  return 7;
}

Future.delayed

  • 지연 시간을 생성할 때 사용한다.
  • 지연 시간이 완료될 후 실행될 함수를 인자로 넣을 수 있다.
void main() {
  Future.delayed(const Duration(seconds: 3), () {
    print("3초 지남!!!");
  },);
}

Future.any

  • Future 메소드들을 배열로 받는다.
  • 가장 먼저 완료된 Future의 결과만 반환한다.
  • Future.any의 결과와 별개로 내부의 Future 메소드들은 멈추지 않고 계속 실행된다.
void main() {
  Future.any([
    Future.delayed(const Duration(seconds: 1), (){ return 1;}),
    Future.delayed(const Duration(seconds: 2), (){ return 2;}),
    Future.delayed(const Duration(seconds: 3), (){ return 3;}),
  ]).then((value) {
    print("반환된 값 : $value");
  },);
}

출력 결과

반환된 값 : 1

Future.wait

  • Future 메소드들을 배열로 받는다.
  • 각 Future들의 결과값을 배열로 반환한다.
  • 선언한 순서와 배열에 저장되는 결과값 요소의 순서는 동일하다.
void main() {
  Future.wait([
    Future.delayed(const Duration(seconds: 1), (){ return 1;}),
    Future.delayed(const Duration(seconds: 2), (){ return 2;}),
    Future.delayed(const Duration(seconds: 3), (){ return 3;}),
  ]).then((value) {
    print("반환된 값 : $value");
  },);
}

출력 결과

반환된 값 : [1, 2, 3]

Stream

  • 시간이 오래걸리는 작업을 기다린 후 결과값을 받아와야하는
    비동기 프로그래밍을 위해 만들어진 제네릭 클래스
  • 미래 어느 시점에서 사용할 수 있는 잠재적인 값 또는 에러
  • 데이터나 이벤트가 들어오는 통로
  • 한 개 이상의 Future들의 조합
  • 메소드 처리 과정 중간중간에 여러 번의 반응이 가능하다.
  • 종류
    • 단일 구독 스트림 (Single Subscription Streams)
      • stream에 최대 1개의 listener를 생성할 수 있다.
    • 방송 스트림 (Broadcast Streams)
      • stream에 여러 개의 listener를 생성할 수 있다.

단일 구독 스트림

  • 하나의 stream에 최대 1개의 listener를 생성할 수 있다.
  • 하나의 stream에 2개 이상의 listener 생성 시 오류가 발생한다.
import 'dart:async';

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

  //listener 생성
  final streamListener = stream.listen((value){
    print("listen value : $value");
  });
  
  //값 전달
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add([5, 6, 7]);
  controller.sink.add(()=>[8, 9, 10]);
}

출력 결과

listen value : 1
listen value : 2
listen value : 3
listen value : 4
listen value : [5, 6, 7]
listen value : Closure ‘main_closure0’

방송 스트림

  • 한 번에 하나씩 처리할 수 있는 개별 메시지를 위한 것
  • stream에서 asBroadcastStream() 메소드를 통해 얻어낸다.
import 'dart:async';

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

  //리스너1 생성
  final streamListener1 = stream.listen((value){
    print("listener1 : $value");
  });

  //리스너2 생성
  final streamListener2 = stream.listen((value){
    print("listener2 : $value");
  });
  
  //값 전달
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
}

출력 결과

listener1 : 1
listener2 : 1
listener1 : 2
listener2 : 2
listener1 : 3
listener2 : 3

Stream 관련 메소드

조건 추가하기

  • where 메소드를 통해 리스너가 받을 수 있는 값에 제한을 둘 수 있다.
import 'dart:async';

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

  //리스너1 생성, 홀수만 출력
  final streamListener1 = stream.where((value) => value % 2 == 0).listen((value){
    print("listener1 : $value");
  });

  //리스너2 생성, 짝수만 출력
  final streamListener2 = stream.where((value) => value % 2 == 1).listen((value){
    print("listener2 : $value");
  });
  
  //값 전달
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
}

출력 결과

listener2 : 1
listener1 : 2
listener2 : 3

지정한 개수만 받기

  • take 메소드를 통해 지정한 횟수만큼만 값을 받을 수 있게할 수 있다.
import 'dart:async';

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

  //리스너 생성
  final streamListener = stream.take(3).listen((value){
    print("listener : $value");
  });
  
  //값 전달
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
}

출력 결과

listener : 1
listener : 2
listener : 3

스킵하기

  • skip 메소드를 통해 지정한 횟수만큼 스킵할 수 있다.
import 'dart:async';

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

  //리스너 생성
  final streamListener = stream.skip(3).listen((value){
    print("listener : $value");
  });
  
  //값 전달
  controller.sink.add(1);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
}

출력 결과

listener : 4
listener : 5

조건을 만족하는 값만 받기

  • takeWhile 메소드를 통해 지정한 조건에 맞는 값만 받을 수 있다.
  • 지정한 조건에 대해서 한 번이라도 false가 나오는 경우에는 반복문이 종료된다.
import 'dart:async';

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

  //리스너 생성
  final streamListener = stream.takeWhile((element) => element % 2 == 0).listen((value){
    print("listener : $value");
  });
  
  //값 전달
  controller.sink.add(0);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
}

출력 결과

listener : 0
listener : 2

조건을 만족하는 값 스킵하기

  • skipWhile 메소드를 통해 지정한 조건에 맞는 값은 스킵할 수 있다.
  • takeWhile과 달리 지정한 조건에 대해서 한 번이라도 false가 나오는 경우에도 반복문이 종료되지는 않는다.
  • 지정한 조건에 대해서 false가 나오면 그 값부터 마지막 값까지 스킵없이 전부 받아들이게 된다.
import 'dart:async';

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

  //리스너 생성
  final streamListener = stream.skipWhile((element) => element % 2 == 0).listen((value){
    print("listener : $value");
  });
  
  //값 전달
  controller.sink.add(0);
  controller.sink.add(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
}

시간 제한 추가하기

  • timeout를 통해서 값을 받아들이기까지의 시간 제한을 추가한다.
  • 리스너가 지정한 시간 안에 데이터를 받지 못하면 에러를 발생시킨다.
    • 에러가 발생하기 하지만 리스너 자체가 종료되지는 않는다.
import 'dart:async';

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

  //리스너 생성
  final streamListener = stream.timeout(const Duration(seconds: 1) ,onTimeout: (sink) {
    print("timeout 발생 / $sink");
  },).listen((value){
    print("listener : $value");
  });
  
  //값 전달
  controller.sink.add(0);
  await Future.delayed(const Duration(seconds: 2));
  controller.sink.add(2);
  await Future.delayed(const Duration(seconds: 2));
  controller.sink.add(3);
  await Future.delayed(const Duration(seconds: 2));
  controller.sink.add(4);
  await Future.delayed(const Duration(seconds: 2));
  controller.sink.add(5);
}

출력 결과

listener : 0 timeout 발생 / Instance of ‘_ControllerEventSinkWrapper' listener : 2 timeout 발생 / Instance of '_ControllerEventSinkWrapper' listener : 3 timeout 발생 / Instance of '_ControllerEventSinkWrapper' listener : 4 timeout 발생 / Instance of '_ControllerEventSinkWrapper' listener : 5 timeout 발생 / Instance of '_ControllerEventSinkWrapper'

Stream.periodic

  • 지정한 시간마다 한 번씩 실행되는 스트림을 생성한다.
  • take 메소드가 없으면 종료되지 않고 계속 반복된다.
void main() {
  Stream.periodic(const Duration(seconds: 1)).take(5).listen((value){
    print("listener : $value");
  });
}

Stream.fromIterable

  • 지정한 컬렉션을 통해서Fu 스트림을 생성한다.
void main() {
  Stream.fromIterable([1, 2, 3, 4, 5]).listen((value){
    print("listener : $value");
  });
}

Stream.fromFuture

  • 지정한 Future 객체에서 스트림을 생성한다.
void main() {
  Stream.fromFuture(Future((){
    return [1, 2, 3];
  })).listen((value){
    print("listener : $value");
  });
}

async*와 yield

async*

  • Stream 객체를 반환하게 하는 키워드
  • 메소드의 파라미터 부분과 몸체 부분 사이에 선언한다.

yield

  • stream에 listener에 지속적으로 값을 반환해주는 키워드
  • 일반적인 메소드에서 값을 반환하는 경우의 return 대신에 사용한다.

예시 (async*와 yield)

void main() {
  streamExample().listen((value) {
    print("listener : $value");
  },);
}

Stream<int> streamExample() async* {
  for(int i=0; i<5; i++){
    yield i;
  }
}

출력 결과

listener : 0
listener : 1
listener : 2
listener : 3
listener : 4

yield*

  • Stream을 통한 재귀 생성 함수 사용하는 키워드

예시 (yield*)

int num = 5;

void main() {
  streamExample().listen((value) {
    print("listener : $value");
  },);
}

Stream<int> streamExample() async* {
  yield num--;
  if(num > 0){
    yield* streamExample();
  }
}

Isolate

Isolate란?

  • 싱글스레드 언어인 Dart를 지원하기 위한 비동기 프로그래밍 기법
  • 다른 언어의 스레드 프로그래밍같은 역할을 한다.

Isolate의 특징

  • Isolate는 스레드와 다르게 메모리를 공유하지 않는다.
  • 각 Isolate마다 고유의 메모리 공간을 가진다.
  • 각 Isolate 내부에 event loop가 있어서 이벤트가 들어올때 마다 이를 처리한다.
  • 하나의 작업을 여러개의 Isolate로 처리하고 싶을때 메세지를 이용하에 Isolate간 통신을 할 수 있다.

기본 형식

import 'dart:isolate';

void main() async {
  Isolate? isolate;

  isolate = await Isolate.spawn<String>((message) { 
    //실행할 내용
  }, '전달할 메시지');
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.