42서울 라피신을 올해 1월에 시작하고 3월에 본과정을 오니 눈 깜짝할 새 반년이 지나가버렸네요.
모두들 원하는 목표를 향해 열심히 달려가시던데 저도 영향을 많이 받고 있습니다. 그렇지만 아직 1서클…
이제 시작해보겠습니다!
이번에는 Dart 문법 중 비동기 처리에 대해서 알아보겠습니다.
Flutter에서는 비동기 처리를 위한 방법이 두가지가 있습니다.
바로 Future와 Stream입니다. 이 둘은 언제 쓰고 어떻게 쓰는지 이번에 알아보겠습니다.
비동기 방식을 사용하는 이유를 알기 위해선 먼저 동기 방식이 어떤지 알아야 합니다.
순차적으로 실행되는 동기 방식에선 어떤 동작을 실행시켰을 때 그 결과가 반환될때까지 대기합니다.
그런데 자원 사용이 많은 처리 때문에 기다리는 동안 다른 동작을 할 수 없어 자원을 효율적으로 사용하지 못했습니다.
이런 방식은 단일 프로그램인 경우에는 별 문제가 없었지만 대규모 웹서비스 같은 곳에서 하는 선착순 이벤트 같은 기능에서는 한 순간에 상당히 많은 요청이 발생했기 때문에 이로 인해 고객이 이탈하는 문제점을 해결하고자 비동기 방식 처리 각광받게 되었습니다.
왜 비동기 방식이 요청이 많아질 때 효율적인지 아래 이미지를 보면 이해가 쉬울겁니다.
동기 방식과 비동기 방식의 처리 비교
위 이미지처럼 비동기 방식을 유용하게 쓰이는 곳은 사실 백엔드입니다.
서버를 향해 날아오는 요청들을 순차적으로 처리하기 보단, 각 요청들을 비동기 방식으로 받아들이고 처리가 끝난 이후 돌려주면 지연시간이 짧아지기 때문입니다.
이외에도 동기 방식의 문제점을 해결하려고 Thread를 여러개 돌리는 방법을 사용하는 것도 하나의 방법이지만, Multi-Threading 방식은 코딩이 굉장히 복잡해지고 숙련된 개발자가 아니라면 다루기 어려운 영역이었습니다.
그럼 Flutter에는 왜 비동기 방식이 존재할까요?
•
비동기 통신으로 다음에 보여줄 데이터를 미리 가져오는 경우
•
요청 보낸 응답을 기다리는 동안 사용자가 앱을 조작할 수 있게 제공
•
각 상황마다 화면을 다르게 보여 줄 수 있음
◦
요청을 기다리는 동안 대기할 화면
◦
응답에 성공하면 가져온 데이터를 보여줄 화면
◦
응답에 실패하면 에러 알람이 뜨거나 화면에 표시
Future
Future는 어떤 함수를 실행시키고 함수 내부에서 순차적으로 작업이 진행되지만, 처리가 된 반환 결과는 함수가 종료가 된 이후에도 원하는 결과를 바로 받았다고 볼 수 없는 상태를 가진 객체입니다.
Future를 사용하는 간단한 코드 예시입니다. 함수를 호출하고 그 결과를 출력하는 내용의 코드입니다.
void main() {
var res = testFuture();
// 출력값: Instance of '_Future<int>'
print(res);
}
Future<int> testFuture() async {
return 1;
}
Dart
복사
void main() async {
var res = await testFuture();
// 출력값: 1
print(res);
}
Future<int> testFuture() async {
return 1;
}
Dart
복사
왼쪽에 코드는 반환 결과를 기다리지 않고 출력하기 때문에 Future의 instance가 보이고 오른쪽은 반환값으로 지정된 1이 나옵니다.
이 두 결과가 다른 이유는 async와 await 때문입니다.
async
기본적으로 함수는 동기적으로 처리한다고 가정하고 있습니다.
그래서 비동기적인 함수를 생성하기 위해선 async 키워드를 함수 선언부 오른쪽 끝, 여는 중괄호 전에 추가해주면 됩니다.
await
비동기 함수를 호출하는 곳에서 다음 처리를 위해 해당 함수 결과를 받을 때까지 대기해야 할 필요가 있는 경우에 사용합니다.
await 키워드를 사용해서 비동기 함수의 처리가 끝난 결과를 받기 위해서는
함수를 정의할 때 async를 달아줘야 합니다.
Stream
이제 비동기 방식 중 Stream에 대해 알아보겠습니다.
Stream은 하나의 함수를 실행시키지만 함수가 한번만 호출되서 끝나는 게 아닌 계속해서 반환값으로 처리된 or 변화되는 값을 return과 비슷한 yield 키워드에 적힌 값을 반환하는 형태의 객체입니다.
void main() async {
await for(int i in testStream())
print(i);
}
Stream<int> testStream() async* {
for(int i = 0; i < 10; i++) {
yield i;
}
}
Dart
복사
void main() {
testStream().forEach(print);
}
Stream<int> testStream() async* {
for(int i = 0; i < 10; i++) {
yield i;
}
}
Dart
복사
두가지 다 같은 출력 결과를 보여줍니다.
async*
반환값이 Stream<T> 형태인 함수는 꼭 async*를 달아줘야 합니다.
옆에 별표가 붙은 것은 Future의 async 키워드와 차이를 주기 위해서 그렇습니다.
yield
하나의 값을 return처럼 반환할 값에 쓰이는 키워드
yield*
yield* 키워드를 이용해 Iterable 또는 Stream 함수를 재귀적으로 사용할 수 있습니다.
void main() async {
await for(int i in testStream(0))
print(i);
}
Stream<int> testStream(int i) async* {
yield i;
if (i < 10)
yield* testStream(i + 1);
}
Dart
복사
Iterable
yield*에서 언급한 Iterable도 알아보겠습니다.
List, Set, Map과 같이 구현된 자료구조로 만들어진 추상화시킨 클래스입니다.
sync*
async*와 대비되는 키워드입니다.
sync*를 활용하는 방법입니다.
void main() {
Iterator<int> it = testIterator().iterator;
while (it.moveNext())
print(it.current);
}
Iterable<int> testIterator() sync* {
int i = 0;
while (i < 10)
yield i++;
}
Dart
복사
마무리
See you again!
비동기 방식은 Dart만의 고유한 문법이 아닌 여러 언어에서도 비슷하게 사용되고 있습니다.
이번에 간단히 알아가시고 정말 사용하실 때 여러 글들을 참고하시면서 문제를 해결하시면 될 것 같습니다!