Asynchronous Programming

mmmYoung·2023년 5월 16일
0

플러터

목록 보기
11/11
post-thumbnail

Synchronous Programming


우선 CPU의 스레드에 대해 알아보자. 스레드란 작업을 하는 가장 작은 유닛을 말한다.
위의 이미지를 보자.
1+1이라는 작업1을 실행하려고 한다. 작업 1이 시작되면 1+1이라는 계산을 CPU가 해야하고,작업 1이 완료될 때 까지 CPU를 사용할 수 없다.
작업 1이 완료되면 다음 작업인 작업 2(2+2) 를 시작하게 되고, 마찬가지로 완료될 때까지 CPU를 사용할 수 없다.

이것이 문제가 될 때는 서버요청을 받게 될 때이다. 아래 이미지를 보자.

어떤 서버에 접근하여 주소 정보를 가져온다고 가정하다. 서버 요청을 하고 원하는 데이터를 받아오기까지에는 당연스럽게도 시간이 필요하다. 즉 이 과정을 하나의 작업이라고 보면, 서버에 요청하여 데이터를 받아올 때 까지 CPU를 사용할 수 없다는 말이 된다. CPU가 일을 안하고, 단순히 서버응답을 기다리는 시간에도 CPU를 사용할 수 없다. 이것이 디폴트이다.

Asynchronous Programming


Dart는 기본적으로 비동기 프로그래밍 언어이다.
동기적 프로그래밍과 비교해서 보자면, 작업 1이 시작되어 서버 요청을 하면, 곧바로 CPU를 사용할 수 있게 된다. 왜냐? 서버 요청 이후 기다리는 작업에는 CPU가 필요없기 때문.

위 코드를 실행시켜보면 반드시 1+1을 계산하고, 결과값이 나온 후 2+2를 계산하게 된다. 항상 이 순서!

Future

Future는 미래에 받아올 값을 의미한다.

Future.delayed 함수를 실행해보면 async programming이 가능해진다.


실행 결과를 보면 비동기적이라는 것을 증명할 수 있다.

만약 동기적 프로그래밍이였다면, Future.delayed에서 기다리는 동안 CPU를 사용할 수 없어지기 때문에 "함수 완료" 출력문도 계속 기다리게 되었을 것이다.

하지만 비동기적인 프로그래밍이기 때문에 2초동안 기다린다는 명령을 받자마자 다음 명령어들을 실행하고, 2초가 지난 뒤 다시 돌아와서 계산 완료 출력을 실행하게 된다.

그렇다면 addNumbers를 두번 실행시켜보자 !

조금 헷갈릴 수 있지만 똑같은 원리이다. 1+1 함수가 실행되면서
계산 중, 함수 완료 출력문이 출력되고 2초 뒤 계산 완료가 뜰 것이다.
하지만 2초가 지나기 전에 다음 명령어인 addNumbers(4,4)를 실행하게 되어, 계산 중, 함수 완료 출력문이 출력된다. 최종적으로는 1+1의 계산 완료가 뜨고, 4+4의 계산 완료가 뜨게 된다.

await / async

난 계산 중 -> 계산 완료 -> 함수 완료의 순서로 띄우고 싶은데...
그래서 생겨난 것이 await 키워드이다.
await를 사용하기 위해서는 반드시 함수에 async를 표기해주어야 한다.

2초간기다리는 Future.delayed에 await를 붙여주었더니 원하는 순서대로 출력 결과가 나온다 !
출력을 따라 자세히 실행 순서를 살펴보자.
먼저 addNumbers(1,1) 함수가 호출되면서, 계산 중 : 1 + 1 이 출력되고 Future.delayed가 실행되며 2초를 기다린다.
이 때 기다리는 동안 CPU를 사용하지 않는다고 하였으니, 그 동안 addNumbers(4,4)가 실행되어 계산 중 : 2 + 2를 출력하고 또 여기서의 Future.delayed에서 기다리는 일을 하게 되는 것이다.
그리고 addNumbers(1,1) 함수 안에서 기다리는 2초가 지나 계산 완료 2, 1+1 함수 완료 출력 문이 차례대로 출력된다.
마지막으로는 addNumbers(2,2) 함수의 명령인 계산 완료 8, 4 + 4 함수 완료가 출력된다.

이게 아니다! 난 addNumbers(1,1)이 끝나고 다음 함수를 띄우고 싶다!

위에서는 addNumbers(1,1)과 addNumbers(4,4)가 동시에 실행된다. 그치만 addNumbers(1,1)의 모든 작업을 마치고 addNumbers(4,4)를 하고싶을 때는 어떻게 하면 될까?

이번에는 addNumbers에 await를 붙여주자!

함수에 await를 붙이려면 해당 함수의 리턴값에는 반드시 Future가 있어야 한다.
이렇게 하니 addNumbers(1,1)가 종료된 후 addNumbers(4,4)가 실행된다.

Stream

앞서 살펴본 Future는 위와 같이 실행된다. 무언가가 실행되면, await를 통해 기다리고 반환 값을 전달받게 된다. 하나의 함수에서는 하나의 값만을 받아올 수 있다. 즉 내가 여러 번의 실행에서 여러 개의 값을 받아오고 싶다면 함수를 여러 번 실행해야하는 것 밖에 없다.

그것을 해결하기 위해서 만들어진 것이 Stream! Stream에서는 데이터가 흐른다. 계속해서 값을 무한하게 받을 수가 있다. 완료되기 전까진!

이번엔 직접 사용해보자.

StreamController를 지정해주고, listen 함수를 통해 전달받은 값이 들어오는 대로 출력해준다.

controller.sink.add를 이용해 값을 새롭게 넣어주면, 그대로 출력하는 것을 볼 수 있다.

리스너를 하나 더 추가하고 싶다!

이것도 가능하다! 이때는 stream에 asBroadcastStream()을 붙여줘서 여러 개의 리스너를 만들 수 있도록 해야한다.

where 키워드로 데이터 필터링하기

즉석에서 stream 값을 바꾸는 방법도 있다.
만약 스트림에서는 1,2,3,4,5가 주입되고 리스너1에서는 짝수만, 리스너2에서는 홀수만 받아오고 싶다면, where를 이용하여 조건을 추가할 수 있다.

이번에는 함수로 스트림을 주는 방법에 대해서 알아보자

Future 함수를 만들 때와 유사하다.
대신, Future -> Stream
async -> async*
return -> yield
이정도로 바꾸어주면 된다.

이렇게 실행해보면 yield로 값이 나오고, 순서대로 출력하게 됨!

async*에서도 future 함수를 쓸 수 있을까?

ㅇㅇ 있다.
그냥 똑같이 await 붙여주면 됨..

위의 함수를 실행해보면, calculate(2)와 (4)의 첫번째 값인 0,0이 동시에 나온다. 그리고 await Future.delayed로 2초를 기다리고, 두번째 값인 2,4가 또 동시에 나온다.

이번에도 calculate(2)가 끝나고 calculate(4)를 실행시키고 싶다!

Future에서의 await과 같은 기능을 Stream에서는 어떻게 쓸 수 있을까?
yield* 를 사용하면 된다!
우선 Stream 함수를 하나 더 만들고 확인해보자.

playAllStream 함수에는 yield* calculate() 함수를 실행하도록 되어 있다. 이렇게 작성하면 calculate(1)이 완료된 이후 calculate(1000)을 실행하게 된다.

출처 https://www.youtube.com/watch?v=rk41rBXq3zQ
profile
안냐세여

1개의 댓글

comment-user-thumbnail
2023년 5월 16일

잘보고 갑니다. 덕분에 이해가 됐습니다!

답글 달기