G.frege를 너무 사랑하는 holy가...

[dart] practice4- async programming

[ document summary ]
    Title: [dart] practice4- async programming
    date: 2023 10.23
    content: async programming 알아야할 최소한의 것들.

동기와 비동기

동기

순차적으로 코드가 실행되면, 동기화 program이라고 한다. 동기화 program은 하나의 코드 instruction이 실행이 끝난 후, 즉 완료한 다음에 다음 instruction이 시작 된다.

비동기

비동기 program은 instruction이 실행이 완료되기전에 다음 instruction이 실행 된다.

동기와 비동기의 장단점

동기

장점은 어떤 instruction의 결과값을 다음 instruction이 이용하기때문에 자연스러운 방식이다. 단점, cpu를 사용하지 않고 i/o stream의 경우 lock이 걸리는 모습을 보인다.

비동기

비동기 프로그램은 이전 instruction이 끝나기도 전에 다음 instruction이 시작된다. 장점은 cpu를 사용하지 않는 서버연결과 같은 작업으로 인한 lock이 걸리는 모습이 보이지 않는다는 것이다. 단점은 코드 실행이 뒤죽박죽이 되어 버린다.

동기와 비동기 mechanism

동기

동기는 일반적인 프로그래밍이라서 call stack을 이용한다.

우리의 목적

동기방식의 단점을 Future를 사용해서 없애고, 비동기의 단점을 async & await로 없애자는 게 목적이다. 즉 cpu자원 낭비와 lock이 걸린것처럼 보이는 문제를 해결하기 위해선 무조건 비동기를 써야 한다. 하지만, 비동기는 뒤죽박죽이기 때문에 동기방식이 갖는, 순차적 실행결과를 이용하게 처리해야 한다. 요약하면 실행하면 동기처럼 실행하지만, 내부적으로는 비동기로 동작하는 코드를 작성하는게 우리의 목표다.

비동기가 필요한 이유

cpu를 사용하지 않는 작업들, I/O stream을 연결해서 사용하는 작업들, socket, file과 같은 작업들은 cpu를 사용하지 않고 언제 끝날지 알수 없다. program이 이런 코드들을 실행하면 block된다. lock이 걸린다.는 표현을 한다. 허송세월 기다리는 이런 코드들을 비동기로 처리해야 한다. flutter의 경우 app프로그램을 짠다. app에서 반응속도는 매우 중요하다. 즉 비동기 프로그램은 flutter에서 없어서는 안되는 기본이다.

[1- future] 비동기가 필요한 상황 simulation (동기화 코드)

  • 비동기 program을 할수 밖에 없는 상황을 우선 만들어야 한다. block되는 상황을 만들고, 그런 다음에 여기서 비동기 programming을 가능케하는 keyword를 붙여서, 와! 비동기가 되네하는 그런 느낌을 피부로 느끼게 해야한다.
  • block되는 상황을 만드는 code는 sleep()가 있다. 그런데 강의에선 delayed()로 예를 든다. 왜냐하면 Future가 제공되기 때문이다. 즉, Future.delayed()는 sleep()의 비동기 코드다. 반면 sleep()는 동기화 코드다.
import 'dart:io';
void main(){
  addNumbers(3,5);
}
void addNumbers(int number1, int number2){
  print("계산 시작: $number1 + $number2");
  sleep(Duration(seconds: 2));
  print("계산 완료: $number1 + $number2");
}

addNumbers를 계산할 때, sleep코드를 실행하면 block이 된다. sleep이 socket통신이나, file i/o stream이라고 가정해서 테스트했지만, 동기화 코드는 화면이 lock이 걸린것처럼 된다.

[2- future] 비동기가 필요한 상황 simulation 해결(비동기 코드)

  • sleep대신에 Future.delayed()를 사용하면 비동기로 처리할 수 있다.
  • sleep을 서버와의 통신이나 시간이 오래걸리는 file작업을 은유한 코드지만, 어쨋든 동일하다. 이런 코드들은 비동기로 처리해야 한다. 그래야 화면에 lock이 걸리지 않는다.
  • 참고로 future.delayed()는 duration객체와 callback function을 인자로 받을 수 있다.
void main(){
  addNumbers(3,2);
}
void addNumbers(int num1, int num2){
  print("before calculate");
  Future.delayed(Duration(seconds:2),(){
      print("calculating");
    }
  );
  print("after calculate");
}

[3- future] 비동기 코드의 문제점과 해결 (await & async)

  • 위에서 Future.delayed()는 비동기로 처리되었다. 바로 다음 명령어가 실행되었기 때문이다.
  • 그런데 다음 명령어가 오래걸리는 future 비동기 명령어와 연관관계가 있다면 문제가 생긴다. 예를 들어서 server에서 data를 가져와서 그 데이터를 조작하는 명령이 다음 명령어라면, 무조건 data가 있어야 그 명령어가 사용된다. 그러면 다시 동기화 프로그래밍을 해야 하는가?
  • 그런 경우가 의외로 많다. 그렇다고 동기화하면 lock이 걸리는 현상을 보일 것이다. 어떻게 할것인가? 그래서 await와 async라는 keyword가 사용된다. Future가 사용되는 block앞에 async라는 keyword를 넣고, Future에는 await keyword를 넣는다. 이렇게 하면 await 이하 명령어들은 겉으로 봤을때는 동기화 program처럼 순차적으로 실행된다. 하지만 실제는 비동기로 실행된다.
  • 즉 async,await를 사용하면 비동기 코드의 결과값을 사용하는 다음 명령어가 비동기 코드의 결과값을 가지고 조작 연산을 가능하게 보장한다는 것이다.
  • await이하를 하나의 묶음으로 봐도 된다.
void main(){
  addNumbers(3,3);
}

void addNumbers(int num1, int num2) async{

  print("before calculate");
 await Future.delayed(Duration(seconds:2), (){
      print("calculating..");
      });
  print("after calculating");

}

[4- future] 비동기로 실행되는 함수의 문제점

  • 우리의 목표는 동기처럼 실행되지만, 내부적으로는 비동기적으로 돌아가는 코드를 만드는 것이다. 여기서는 뒤죽박죽 비동기처럼 실행된다. addNumbers(1,1)을 실행하면 동기처럼 실행된 후 , addNumbers(3,3)이 뒤이어서 실행되는 모습을 만들기 위해선 addNumbers를 async & await로 처리해줘야 한다.
void main(){
  addNumbers(1,1);
  addNumbers(3,3);
}

void addNumbers(int num1, int num2) async{
  print("before calculating: $num1, $num2");
  await Future.delayed(Duration(seconds:2),(){
      print("calculating...$num1,$num2");
      });
  print("after calculating: $num1, $num2");
}

[5- future] 비동기로 실행되는 함수의 문제점 해결

  • 비동기 코드를 사용하는 함수를 사용하면 비동기처럼 뒤죽박죽 실행된다. 비록 비동기 코드를 사용하지만, 실행될때 마치 동기화된 코드처럼 실행하는게 우리가 비동기코드를 사용할때 목표, 지향점이다.
  • addNumbers를 실행하면 뒤죽박죽 실행되는것을 볼 수 있다.
  • 위의 코드를 동기화 프로그램이 실행되는 것처럼 보이게 하려면 async await를 사용해야 한다.
void main() async {
  await addNumbers(2,2);
  await addNumbers(3,3);
}

Future<void> addNumbers(int num1, int num2) async {
  print("before calculate $num1,$num2");
  await Future.delayed(Duration(seconds:2),(){
      print("calculating $num1,$num2");
      });
  print("after calculate $num1,$num2");
}

[6-stream] stream의 개념

  • stream은 Future처럼 언제 끝날지 모르는 작업을 처리하는 방식이다. 원격서버와 연결해서 file을 가져온다면 그냥 Future를 사용하면 되지만, 채팅을 한다고 생각해보면, future를 사용할 수 없다. 왜냐면 끝이 없기 때문이다. 또한, 이 때 Stream을 사용한다. stream은 채팅처럼 끝이 없고, 상대방이 보낸 메시지를 그때 그때 처리한다는 특징이 있다. 이것을 yield로 구현하겠지만, 이런 특징을 가지고 있다는 것만 알자. stream이란 용어는 예전부터 있던 용어다. socket이나 file 입출력작업을 모두 I/O Stream이라고 불렀다.
  • dart에선 stream을 observer pattern으로 구현했다. observer pattern은 youtube구독 시스템과 비슷하다. subject가 동영상을 올리면 등록했던 관찰자들에게 noti가 간다. 비슷하게 Dart에선 StreamController의 sink와 stream이란게 있다. sink는 subject에 해당한다. sink에서 data를 게시하면, observer에 해당하는 stream에게 noti가 가고, stream은 listener를 통해서 데이터를 받을 수 있다.
  • 요약하면, 두가지 특징이 있다. observer pattern과 yield다.

[7-stream] 간단한 stream의 예.

  • stream을 사용하려면 StreamController가 필요하다. dart:async를 import한다.
  • streamController가 가진 sink와 stream을 꺼내서 data를 올리고 noti받는다.
  • observer pattern을 이용하는 법을 배운다.
import 'dart:async';
void main()
{
  final control = StreamController();
  final stream  = control.stream;

  final streamListener1 = stream.listen((val) {
      print(val);
      });

   control.sink.add(1);
   control.sink.add(2);
   control.sink.add(3);
   control.sink.add(4);
   control.sink.add(5);
}

[8-stream] 여러명의 구독자(관찰자,stream)이 있는 경우

  • stream은 youtube에서 구독자에 해당한다고 했다. 그런데 구독자가 여러명을 하려면 stream이 asBroadcastStream()를 호출해야 한다.
import 'dart:async';
void main(){
  final control = StreamController();
  final stream  = control.stream.asBroadcastStream();

  final listener1 = stream.listen((val){
      print('listener1: $val');
      });
  final listener2 = stream.listen((val){
      print('listerner2: $val');
      });

  control.sink.add(1);
  control.sink.add(2);
  control.sink.add(3);
}

[9-stream] 2개의 streamlistener에서 하나는 짝수만, 하나는 홀수 출력

  • where을 사용해서 처리한다.
import 'dart:async';
void main(){
  final control = StreamController();
  final stream = control.stream.asBroadcastStream();

  final listerner1 = stream.where((x) => x %2 == 0 ).listen((value){
      print("listener1 : $value");
      });
  final listerner2 = stream.where((x) => x %2 ==1).listen((value){
      print("listener2 : $value");
      });

  control.sink.add(1);
  control.sink.add(2);
  control.sink.add(3);
  control.sink.add(4);
  control.sink.add(5);

}

[10-stream] yield simulation.

  • 지금까지는 observer pattern 관점에서 stream을 테스트했다.
  • stream의 다른 한가지 특징인 yield에 대해서 테스트 할것이다.
  • 채팅같은 stream에서는 상대방의 메시지가 올때마다 보여주는 처리를 해야하기 때문에 yield가 사용된다.
  • yield를 simulation하기 위해서 for-loop을 사용해보자.
  • for loop로 5번 return하게 만들자.
  • 불가능하다. for-loop로 5번 return을 불가능하기 때문이다.
import 'dart:async';
void main(){
  print(calculate(10));

}
int calculate(int num){
  for (int i =0; i< 5; i++){
    return i * num;
  }
}

[11-stream] for-loop를 stream으로 만들기.

  • 정상적인 상황이라면 for-loop을 stream처럼 사용하기 어렵다. 그런데 stream을 만들면 된다.
  • stream은 lis
import 'dart:async';
void main(){

}

int calculate(int num){

}