Dart 언어 오답노트 : 35. Stream

샤워실의 바보·2023년 11월 3일
0
post-thumbnail

기본 설명

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(2);
  controller.sink.add(3);
  controller.sink.add(4);
  controller.sink.add(5);
}

이 Dart 코드는 StreamController를 생성하고 이를 사용하여 스트림에 데이터를 추가한 후, 한 리스너가 해당 데이터를 수신하도록 설정하고 있습니다. 각 코드 부분의 기능은 다음과 같습니다:

  • StreamController: 스트림을 생성하고 데이터를 추가하는 방법을 제공하는 객체입니다. 컨트롤러는 데이터를 추가하는 sink와 데이터를 수신하는 stream을 가지고 있습니다.

  • stream: 데이터가 추가될 실제 스트림입니다. StreamController에서 얻은 스트림으로, 리스너가 구독하여 데이터를 수신할 수 있습니다.

  • streamListener1: 스트림에 대한 구독입니다. 스트림에 listen을 호출하면 구독이 생성됩니다. 여기서는 스트림이 새로운 데이터를 발행할 때 수행할 작업을 정의하고 있습니다. 이 경우 'Listener 1 :'이라는 접두사와 함께 데이터를 출력합니다.

  • controller.sink.add(value): 스트림에 데이터를 추가하는 방법입니다. sink는 스트림의 입력 부분이며, 여기서는 1부터 5까지의 숫자를 추가하고 있습니다.

프로그램의 흐름은 다음과 같습니다:

  1. StreamController가 생성됩니다.
  2. StreamController로부터 stream을 얻습니다.
  3. stream.listen(...)으로 stream에 구독합니다.
  4. controller.sink.add(value)를 사용하여 스트림에 값(1에서 5까지)을 추가합니다.

프로그램이 실행되면 controller.sink.add를 통해 추가된 각 숫자가 스트림을 통해 발행되고, streamListener1에 의해 출력됩니다. 여기서는 하나의 리스너만 있으므로 각 값이 스트림에 추가될 때마다 그 값이 출력됩니다.

Dart에서 Stream이라는 이름은 연속적인 데이터의 흐름을 의미합니다. 데이터 스트림은 여러 개의 데이터 이벤트를 순차적으로 제공하는 시퀀스입니다. 이런 면에서 보면, 실제로 물이 흐르는 'stream'과 비슷한 개념으로 생각할 수 있습니다: 데이터가 시간에 따라 흘러가며, 구독자는 그 데이터를 받아 처리할 수 있습니다.

sink 메서드는 Stream에 데이터를 "넣는" 역할을 합니다. 실세계에서의 "sink"는 물이나 다른 물질이 들어가는 장소를 의미하는데, 이 경우 프로그래밍에서의 sink는 데이터 스트림에 데이터를 "배출"하는 입력 지점을 의미합니다. StreamControllersink는 데이터를 스트림으로 푸시하는 인터페이스 역할을 하며, 이 데이터는 후에 스트림을 구독하고 있는 리스너들에 의해 소비됩니다.

간단히 말해서, Stream은 데이터의 흐름을 나타내고, sink는 그 흐름에 데이터를 추가하는 메커니즘을 제공합니다. Dart의 스트림은 비동기 프로그래밍에서 중요한 역할을 하며, 데이터가 연속적으로 생성되고 처리되는 다양한 시나리오에서 사용됩니다.

콘솔에 출력된 값은 다음과 같습니다.

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((value) => print('Listener 1 : $value'));

  final streamListener2 =
      stream.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);
}

이 Dart 코드 조각은 StreamController를 사용하여 방송 스트림(broadcast stream)을 만들고, 두 개의 리스너로 해당 스트림을 구독하는 방법을 보여줍니다. 방송 스트림은 여러 개의 리스너가 동시에 동일한 데이터 시퀀스를 받을 수 있도록 합니다.

다음은 이 코드가 수행하는 작업의 단계별 설명입니다:

  1. StreamController 인스턴스를 생성합니다. 기본적으로 스트림 컨트롤러는 단일 구독 스트림(single-subscription stream)을 생성합니다. 이는 한 번에 하나의 리스너만을 허용하는 스트림을 의미합니다.

  2. .asBroadcastStream() 메서드를 호출하여 방송 스트림으로 변환합니다. 이렇게 하면 여러 리스너가 스트림에 구독할 수 있게 됩니다.

  3. 첫 번째 리스너(streamListener1)를 등록하여 스트림의 데이터를 수신하고 출력합니다.

  4. 두 번째 리스너(streamListener2)를 등록하여 동일한 스트림의 데이터를 동시에 수신하고 출력합니다.

  5. controller.sink.add() 메서드를 사용하여 스트림에 숫자 1부터 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

리스너의 실제 출력 순서는 시스템의 스케줄링과 Dart의 이벤트 루프에 따라 달라질 수 있지만, 위의 순서대로 출력되는 것이 일반적입니다.

where 메서드와 함께 사용하기

import 'dart:async';

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

  final streamListener1 = stream
      .where((value) => value % 2 == 0)
      .listen((value) => print('Listener 1 : $value'));

   final streamListener2 = stream
      .where((value) => value % 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);
}

이 코드는 Dart의 스트림과 함께 필터링 연산을 수행하는 방법을 보여줍니다. 두 리스너는 .where() 메서드를 사용하여 서로 다른 조건에 따라 데이터를 필터링합니다.

streamListener1은 짝수 값만 받아서 출력합니다 (value % 2 == 0). streamListener2는 홀수 값만 받아서 출력합니다 (value % 2 == 1).

코드가 수행되면 다음과 같은 작업이 일어납니다:

  1. StreamController가 생성되고, stream 변수는 이 컨트롤러의 스트림을 참조합니다.
  2. asBroadcastStream()을 호출하여 방송 스트림을 생성합니다, 이렇게 함으로써 여러 리스너가 동시에 스트림에 구독할 수 있습니다.
  3. streamListener1streamListener2는 스트림을 필터링하고, 각각 짝수와 홀수만을 수신하도록 설정됩니다.
  4. controller.sink.add()를 통해 스트림에 데이터를 추가합니다. 각 데이터 항목은 해당하는 리스너에 의해 수신되고 출력됩니다.

이 코드가 실행될 때, 출력은 다음과 같을 것입니다:

Listener 2 : 1
Listener 1 : 2
Listener 2 : 3
Listener 1 : 4
Listener 2 : 5

각 리스너는 설정된 필터 조건에 맞는 데이터만을 수신합니다. 여기서는 streamListener1이 짝수를, streamListener2가 홀수를 출력합니다.

yield, async*

import 'dart:async';

void main() {
  calculate(1).listen((value) => print('calculate(1) : $value'));
}

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

이 Dart 코드는 비동기 스트림 생성과 비동기 반복(async*) 및 yield 키워드를 사용하여 스트림에 값을 방출하는 방법을 보여줍니다. calculate 함수는 주어진 숫자(number)로 0부터 4까지의 숫자를 곱하여 스트림을 통해 방출합니다.

여기서 calculate 함수는 async*를 사용하여 정의됩니다. 이는 함수가 비동기 스트림을 반환할 것임을 나타냅니다. 함수 내에서 yield 키워드는 스트림에 값을 하나씩 방출합니다. 이 함수는 0부터 시작하여, 매 반복마다 i를 입력 파라미터인 number와 곱한 결과를 스트림에 방출합니다.

main 함수에서는 calculate 함수에 1을 인자로 넘겨 호출하고, 반환된 스트림에 리스너를 등록합니다. 이 리스너는 스트림에서 새 값을 받을 때마다 해당 값을 콘솔에 출력합니다.

코드 실행 결과는 다음과 같습니다:

calculate(1) : 0
calculate(1) : 1
calculate(1) : 2
calculate(1) : 3
calculate(1) : 4

각 출력은 calculate 함수에서 생성된 스트림에 yield로 추가된 값입니다. 여기서는 0부터 4까지의 값을 각각 1(함수 호출 시 주어진 number)과 곱한 값이 출력됩니다.

Dart에서 yieldasync* 키워드는 스트림을 생성하고 관리하는 데 사용됩니다. 이들은 비동기 프로그래밍에서 중요한 역할을 하며, 특히 데이터를 연속적으로 생성하고 소비하는 경우에 유용합니다.

import 'dart:async';

void main() {
  calculate(1).listen((value) => print('calculate(1) : $value'));

  calculate(2).listen((value) => print('calculate(2) : $value'));
}

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

    await Future.delayed(Duration(seconds: 1));
  }
}

이 코드는 Dart 프로그래밍 언어의 비동기 스트림 생성과 관련이 있습니다. Dart에서 async*yield 키워드는 비동기 스트림 생성자를 정의하는 데 사용됩니다.

아래는 코드의 주요 부분에 대한 설명입니다:

  1. async*:

    • async* 키워드는 비동기 스트림 생성자를 나타냅니다. 이는 함수가 Stream을 반환하며 스트림에 여러 이벤트를 비동기적으로 추가할 수 있음을 의미합니다.
  2. yield:

    • yield 키워드는 스트림에 값을 추가합니다. 이 코드에서는 for 루프를 통해 0부터 4까지 순회하면서 number 변수에 i를 곱한 값을 스트림에 추가합니다.
  3. await Future.delayed:

    • await 키워드와 함께 사용되는 Future.delayed는 비동기 지연을 생성합니다. 이 경우 각 이벤트 사이에 1초의 지연을 추가하여, 스트림 소비자가 새 값을 받기까지 1초씩 기다립니다.
  4. calculate(1).listencalculate(2).listen:

    • main 함수에서는 calculate 함수를 두 번 호출하여 두 개의 스트림을 생성하고, 각각의 스트림에 대해 listen 메소드를 호출하여 값을 출력합니다. 이 메소드는 스트림에서 값을 받을 때마다 콜백 함수를 실행합니다.

결과적으로 이 코드는 두 개의 비동기 스트림을 생성하고, 각 스트림은 1초 간격으로 0부터 4까지의 숫자를 받은 number로 곱한 값을 생성합니다. 첫 번째 스트림은 1을 곱한 값들(0, 1, 2, 3, 4)을, 두 번째 스트림은 2를 곱한 값들(0, 2, 4, 6, 8)을 출력합니다.

async*

async* 키워드는 비동기 스트림 생성기를 나타냅니다. async*를 함수에 사용하면 함수는 Stream을 반환하게 되며, 이 스트림은 비동기적으로 여러 값을 방출할 수 있습니다. async*로 정의된 함수 내부에서는 yieldyield*를 사용해 값을 스트림에 추가할 수 있습니다.

yield

yield 키워드는 async* 함수에서 사용됩니다. 이 키워드는 스트림에 단일 값을 추가하고, 스트림의 소비자에게 값을 제공합니다. 스트림의 리스너는 yield를 통해 방출된 각 값에 대해 지정된 콜백을 실행합니다. yield 다음에 오는 표현식이 평가되면 그 값은 스트림의 구독자에게 전송되고, 함수의 실행은 다음 yield가 호출되거나 스트림이 닫힐 때까지 일시 중단됩니다.

예제

Stream<int> countStream(int to) async* {
  for (int i = 1; i <= to; i++) {
    yield i; // 스트림에 i 값을 추가한다.
  }
}

위 예제에서 countStream 함수는 주어진 to 값까지 숫자를 방출하는 스트림을 생성합니다. 호출자는 이 스트림을 구독하고, yield를 통해 방출된 각 값에 대한 액션을 수행할 수 있습니다.

yield* (yield-each)

yield*는 다른 비동기 스트림의 모든 값을 현재 스트림에 추가하는 데 사용됩니다. yield* 다음에 오는 스트림의 모든 값이 현재 스트림에 방출되어, 두 스트림이 하나로 연결되는 효과가 있습니다.

예제

Stream<int> countStream(int to) async* {
  if (to > 0) {
    // 재귀적으로 countStream을 호출하고 현재 스트림에 모든 값을 추가한다.
    yield* countStream(to - 1);
  }
  yield to;
}

위 예제에서는 yield*를 사용하여 재귀적으로 스트림을 구성하고 있습니다. 각 재귀 호출에서 현재까지의 스트림에 to - 1까지의 값을 추가하고, 마지막에 to 값을 스트림에 추가합니다.

이러한 방식으로 async*yield는 Dart의 비동기 프로그래밍에서 강력한 도구로 작용하여, 복잡한 비동기 데이터 스트림을 쉽고 효율적으로 생성하고 관리할 수 있게 합니다.

import 'dart:async';

void main() {
  playAllStream().listen((value) => print(value));
}

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));
  }
}

이 코드는 calculate 스트림을 이용해 두 개의 스트림을 연결하는 playAllStream이라는 새로운 비동기 스트림 생성자를 만들고 있습니다. 각 calculate 스트림은 숫자를 0부터 4까지 순회하면서 해당 숫자에 매개변수로 받은 number 값을 곱해 이벤트를 발생시킵니다. 각 이벤트는 1초 간격으로 발생합니다.

주요 부분은 다음과 같습니다:

  1. playAllStream 함수:

    • 이 함수는 async* 키워드를 사용하여 정의된 비동기 스트림 생성자입니다.
    • yield* 표현은 다른 스트림의 모든 이벤트를 현재 스트림으로 전달합니다.
  2. yield* calculate(1);yield* calculate(1000);:

    • 첫 번째 줄은 calculate(1) 스트림에서 발생하는 모든 이벤트를 playAllStream 스트림으로 전달합니다.
    • 두 번째 줄은 calculate(1000) 스트림의 이벤트를 전달합니다.
  3. calculate 함수:

    • async*를 사용하여 정의된 비동기 스트림 생성자입니다.
    • 이 함수는 숫자 0부터 4까지 순회하면서 각 숫자에 number 값을 곱한 결과를 스트림에 추가합니다.
    • await Future.delayed를 통해 각 이벤트 발생 사이에 1초의 지연이 있습니다.

결과적으로 playAllStream을 구독하는 리스너는 calculate(1)에서 발생하는 모든 이벤트(0, 1, 2, 3, 4)를 순차적으로 받고, 그 다음에 calculate(1000)에서 발생하는 이벤트(0, 1000, 2000, 3000, 4000)를 순차적으로 받게 됩니다. 각각의 yield 이벤트 사이에는 1초의 지연이 있습니다.

profile
공부하는 개발자

0개의 댓글