본문 바로가기
flutter 공부 기록소

Flutter 공부 기록 6

by riddleuscode 2023. 6. 18.

이번에는 비동기 프로그래밍에 대해 알아보겠습니다.

 

새로 Flutter를 배우면서 지금까지 프로그래밍 한 방식은 동기 방식입니다.

코드를 작성한 순서대로 실행되는데, 요청이 있으면 반드시 응답을 받고 다음 요청을 실행합니다.

" { "를 요청, " } "를 응답이라고 한다면

{ } { } { }

이런식으로 실행되죠.

그런데 비동기 방식은 요청이 있어도 응답을 받기 전에 다른 요청을 보낼 수 있습니다.

{ { } } { }

어런식으로  요청 2번을 보내고 응답을 2번 받을 수도 있는거죠.

바로 사용해보겠습니다.

void main() {
  List<String> sList=["안녕","나는","riddleus"];
  for(int i=0;i<sList.length;i++){
    Future.delayed(const Duration(seconds:1),()=>{
      print(sList[i])
    });
  }


  runApp(const MyApp());
}

결과:

안녕
나는
riddleus 

 

비고: 각 출력에 1초 간격이 있는 것이 아닌, 1초 후에 연달아 출력된다.

 

바로 눈에 띄는 부분은 Future이죠? 이 클래스는 미래에 받아올 값을 뜻한다고 합니다.

이 Future의 delayed는 매개 변수로 (지연시간, 실행할 함수)를 받는다고 합니다.

그리고 동작 역시 실행할 함수를 지연시간 후에 실행합니다.

 

단, 비동기 방식이므로 아래와 같이 동작합니다.

{{{ (1초 지연) }}}

그러므로 약 1초가 지나면 print문 3개가 모두 끝나는 것입니다.

 

그러면, { (1초 지연) } { (1초 지연) } { (1초 지연) } 와 같이 동작하도록 하려면 어떻게 하면 될까요?

async 와 await를 사용하면 됩니다.

Future<void> main() async {
  List<String> sList=["안녕","나는","riddleus"];
  for(int i=0;i<sList.length;i++){
    await Future.delayed(const Duration(seconds:1),()=>{
      print(sList[i])
    });
  }
  
  runApp(const MyApp());
}

결과:

안녕
나는
riddleus 

 

비고: 각 출력에 1초 간격이 있다.

 

async는 함수명 뒤에 붙고, await는 Future 앞에 붙습니다.

await를 붙인 비동기 함수는 async이 붙은 함수에 한해서 붙들고 있습니다.

쉽게 설명하면, 지연시간 1초동안 main(for문)이 돌아가지 않고 있다가, 1초가 다 지나면 다시 main이 돌아가기 시작합니다.

await를 만나면 지연시간동안 async이 붙은 비동기 함수 전체가 같이 기다리는거죠.

 

이번에는 퀴즈를 내보겠습니다.

Future<String> printList(sList) async {
  String merge="";
  for(int i=0;i<sList.length;i++){
    await Future.delayed(const Duration(seconds:1),(){
      merge += sList[i]+" ";
      print(sList[i]);
    });
  }
  return merge;
}


Future<void> main() async {
  List<String> sList=["안녕","나는","riddleus"];
  dynamic merge = printList(sList) ;
  print(merge);
  runApp(const MyApp());
}

이 결과값이 어떻게 나올까요?

 

 

 

(정답 보기 방지)

 

 

 

정답은 아래와 같습니다.

 

결과:

안녕
나는
riddleus

 

이상하게 print(merge) 부분이 출력이 안되었죠?

그 이유는 printList이 return문에 도달하기까지 약 3초가 걸립니다.

그런데, print(merge)를 하는데 걸리는 시간은 0.1초가 안됩니다.

즉, 원하는 결과가 출력되기 위해서는 main을 얼려놓을 필요가 있다는 겁니다.

시점은 어느 시점에서 얼리면 좋을까요?

Future<void> main() async {
  List<String> sList=["안녕","나는","riddleus"];
  dynamic merge = await printList(sList) ;
  print(merge);
  runApp(const MyApp());
}

merge가 제대로된 값을 받을 수 있도록 printList가 시작되는 시점이면 되겠죠?

그러면 3초 후에 다시 main이 제대로 동작해서

 

안녕
나는
riddleus
안녕 나는 riddleus 

 

라는 결과를 얻을 수 있습니다.

 

그럼 이제 제대로 이해가 되었는지 문제를 하나 더 내보겠습니다.

Future<void> main() async {
  List<String> sList=["안녕","나는","riddleus"];
  dynamic merge = printList(sList) ;
  print(1);
  runApp(const MyApp());
}

이걸 돌렸을때 결과를 예측해보세요. (printList는 위에서 사용한 것과 동일합니다.)

 

 

 

 

(정답 보기 방지)

 

 

 

 

결과:

1
안녕
나는
riddleus

 

만약 맞추셨다면 비동기 방식을 제대로 이해하고 계신다고 생각합니다.

요청을 보내면 바로 응답이 온다고 생각하셨으면 1이 마지막에 나온다고 생각하셨을 것 같은데,

아직 동기 방식이랑 헷갈리시는 것 같습니다.

 

이번에는 Stream을 사용해보겠습니다.

import가 필요합니다. 

import 'dart:async';

 

stream을 사용하면 실시간으로 값을 받을 수 있으며, 받았을때 원하는 동작을 하는 함수를 실행시킬 수 있습니다.

아래 예제를 보시고 한번 결과값을 추측해보세요. 

 

Future<String> printList(sList, controller) async {
  String merge="";
  for(int i=0;i<sList.length;i++){
    await Future.delayed(const Duration(seconds:1),(){
      merge += sList[i]+" ";
      controller.sink.add(sList[i]);
    });
  }
  return merge;
}


Future<void> main() async {
  StreamController controller = StreamController();
  dynamic stream= controller.stream;
  final sListener= stream.listen((data){
      print(data);
  });
  
  List<String> sList=["안녕","나는","riddleus"];
  dynamic merge = await printList(sList,controller) ;
  print(merge);
  
  runApp(const MyApp());
}

 

정답을 보여드리기 앞서 설명을 미리 해드리겠습니다.

 

stream을 사용하기 위해서는 위와 같이

1. StreamController 선언

2. StreamController를 통해 stream 가져오기

가 기본적으로 필요하고, 위에서 말한 "실시간으로 값을 받아서 원하는 함수 출력하기"를 위해서는

3. stream.listen을 구현해주시면 됩니다. (final sListener은 없어도 잘 동작합니다.)

 

이제 정답을 보여드리겠습니다.

 

 

 

결과:

안녕
나는
안녕 나는 riddleus 
riddleus

 

비고: 마지막 3,4번째는 거의 동시에 출력됩니다.

 

왜 "안녕 나는 riddleus"가 마지막이 아니라 3번째에 출력되는지 아시겠나요?

stream을 사용하면 실시간으로 값을 받아온다고는 했지만, 지연시간이 전혀 없지는 않을거라 생각하고

그 지연시간이 print(merge)에 도달하는 시간보다 근소하게 더 긴게 아닌가 생각합니다.

다른 의견이 있으시면 얼마든지 환영합니다!

 

기존의 stream은 listen을 하나만 만들어 둘 수 있는데,

만약 여러개를 사용하고 싶으시면 브로드캐스트 스트림을 사용하시면 됩니다.

기본적으로는 기존의 stream과 동일하게 작성하나,

2. StreamController를 통해 stream 가져오기

부분이 조금 다릅니다.

dynamic stream = controller.stream.asBroadcastStream();

 

와 같이 작성합니다.

listen을 여러개 만들 수 있다는 점 빼고는 동일하니 사용하지 않고 넘어가겠습니다.

 

controller를 사용하지 않고 stream을 반환하는 방식도 있는데 아래와 같이 사용합니다.

Stream<dynamic> printList(sList) async* {
  for(int i=0;i<sList.length;i++){
    await Future.delayed(const Duration(seconds:1),(){
    });
    yield sList[i];
  }
}


Future<void> main() async {

  List<String> sList=["안녕","나는","riddleus"];
 printList(sList).listen((data){
    print(data);
  });

  runApp(const MyApp());
}

결과:

안녕
나는
riddleus

 

stream을 반환 할 함수에다가는 async*를 붙여주고, 반환은 yield를 사용합니다.

실제로 사용할때는 매소드 뒤에 .listen()을 붙여 사용하고, 매개변수로 실행할 함수를 넣으면 됩니다.

그리고 아까 설명을 안드렸지만, future.delayed() 사용시

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

 

이런식으로 실행할 함수를 적어주지 않아도 문제가 없고,

이 안쪽에서는 yield를 통해 반환 받을 수 없습니다.

 

이번 글에서는 이걸로 dart관련 내용을 모두 끝냈고, 다음 글부터는 좀 더  flutter앱을 배우는 내용이 포함될 것 같습니다.

감사합니다~

 

 

 

 

 

 

'flutter 공부 기록소' 카테고리의 다른 글

Flutter 공부 기록 8  (0) 2023.06.20
Flutter 공부 기록 7  (0) 2023.06.19
Fluuter 공부 기록 5  (1) 2023.06.17
Flutter 공부 기록 4  (0) 2023.06.16
Flutter 공부 기록 3  (0) 2023.06.15