1. async, awiat와 비동기
async
, await
와 비동기(Asynchronous) 처리 개념은 서로 밀접한 연관이 있으며, 비동기 프로그래밍에서 자주 사용됩니다. 이를 이해하기 위해 각각의 개념과 그 연관성을 설명해보겠습니다.1. 비동기(Asynchronous) 처리란?
비동기 처리란 작업이 완료될 때까지 기다리지 않고 다른 작업을 계속 수행할 수 있는 방식입니다. 이 방식은 네트워크 요청, 파일 읽기/쓰기, 타이머 등 시간이 걸리는 작업을 할 때 유용합니다. 비동기 처리의 가장 큰 장점은 프로그램의 블로킹(blocking)을 방지하여 자원을 효율적으로 사용할 수 있다는 것입니다.
2. async
와 await
비동기 작업을 쉽게 관리하기 위해
async
와 await
는 함수의 실행 흐름을 제어합니다.async
함수: 함수 앞에async
를 붙이면 해당 함수가 비동기 함수가 됩니다. 이 함수는 자동으로Future<T>
타입의 값을 반환하게 되어, 결과를 나중에 받을 수 있습니다.
await
키워드: 비동기 함수 내에서await
를 사용하여 특정 작업이 완료되기를 기다릴 수 있습니다.await
는 해당 비동기 작업이 끝날 때까지 함수 실행을 일시 중단하고, 완료된 결과를 반환합니다. 하지만 이 작업 동안에도 다른 코드들이 계속 실행될 수 있습니다.
3. 비동기 처리의 동작 방식
비동기 함수는 호출 즉시 작업을 시작하지만, 해당 작업이 끝나기 전까지 호출한 곳으로 즉시 반환합니다. 이후 결과가 준비되면 콜백 또는 이벤트 루프를 통해 그 결과를 사용할 수 있게 됩니다.
예시: 비동기 함수와 async
/await
// 비동기 작업 예시
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2)); // 2초 대기
return "데이터 로드 완료";
}
void main() async {
print("데이터 로드 시작");
String result = await fetchData(); // fetchData가 완료될 때까지 대기
print(result); // "데이터 로드 완료" 출력
print("다음 작업 실행");
}
동기와 비동기의 차이
- 동기 처리: 동기 처리는 작업이 완료될 때까지 기다립니다. 모든 작업이 순차적으로 처리되기 때문에, 이전 작업이 끝나지 않으면 다음 작업을 진행할 수 없습니다.
- 예: 전화 통화. 한 사람이 말을 할 때, 다른 사람은 기다려야 함.
- 비동기 처리: 비동기 처리는 작업이 끝날 때까지 기다리지 않고 다른 작업을 수행합니다. 결과가 준비되면 나중에 해당 결과를 처리합니다.
- 예: 문자 메시지. 답장을 기다리지 않고 다른 일을 할 수 있음.
4. 비동기의 장점
- 성능 최적화: 시간이 많이 걸리는 작업(예: 네트워크 요청, 파일 입출력)을 기다리지 않고 다른 작업을 할 수 있어, 자원을 효율적으로 사용할 수 있습니다.
- UI 동작 개선: 비동기 처리를 사용하면 UI가 멈추지 않고 사용자가 계속 인터랙션을 할 수 있습니다.
결론적으로
async
, await
는 비동기 작업을 동기 작업처럼 쉽게 작성할 수 있게 해주며, 비동기 처리의 복잡함을 감추어 더 읽기 쉽고 관리하기 쉬운 코드를 작성하는 데 도움을 줍니다.2. 비동기 함수와 Future<T>타입
1. 비동기 함수란?
비동기 함수는 작업이 완료될 때까지 기다리지 않고 바로 반환되는 함수입니다. 즉, 호출자는 이 함수를 실행한 후에 결과가 준비될 때까지 기다리지 않고 다른 작업을 계속할 수 있습니다. 비동기 함수는 시간이 오래 걸리는 작업(네트워크 요청, 파일 읽기/쓰기 등)을 처리할 때 자주 사용됩니다.
비동기 함수는
async
키워드를 사용하여 선언되며, 함수 내부에서 await
를 사용하여 비동기 작업의 완료를 기다릴 수 있습니다.2. Future<T>
타입이란?
- *
Future<T>
*는 비동기 작업의 결과를 나타내는 객체입니다. 이는 곧 완료될 값 또는 오류를 나타냅니다.T
는 미래에 반환될 값의 타입을 의미합니다. 예를 들어,Future<String>
은 비동기 작업이 끝났을 때 문자열(String
) 값을 반환하는 것을 의미합니다.
Future
의 상태
Future
는 비동기 작업의 결과가 준비되기 전과 후에 다음과 같은 상태를 가집니다:- 대기 중 (pending): 비동기 작업이 아직 완료되지 않았을 때.
- 완료됨 (completed): 비동기 작업이 성공적으로 완료되었을 때. 결과 값을 반환합니다.
- 실패함 (failed): 비동기 작업이 실패했을 때. 예외를 던집니다.
3. 비동기 함수와 Future<T>
의 관계
- 비동기 함수는 항상
Future<T>
타입을 반환합니다.
- 함수에
async
를 붙이면 해당 함수가 비동기 함수가 되며, 내부에서await
로 다른 비동기 작업을 기다릴 수 있습니다.
Future<T>
는 비동기 함수가 완료된 후 나중에 그 결과 값을 제공합니다.
예시: Future<T>
와 비동기 함수
// 비동기 함수
Future<String> fetchData() async {
// 2초 동안 대기하는 비동기 작업
await Future.delayed(Duration(seconds: 2));
return "데이터 로드 완료"; // 작업 완료 후 문자열 반환
}
void main() async {
print("데이터 로드 시작");
// 비동기 함수 호출 후 결과를 대기
Future<String> futureResult = fetchData();
// Future가 완료될 때까지 기다림
String result = await futureResult; // result는 "데이터 로드 완료"가 됨
print(result); // "데이터 로드 완료" 출력
}
4. Future
와 비동기 함수의 실제 동작 방식
- 비동기 함수는 비동기 작업을 처리하고 즉시
Future
를 반환합니다. 이Future
는 비동기 작업이 끝났을 때의 결과를 나중에 제공할 것입니다.
await
키워드를 사용하면 이Future
가 완료될 때까지 기다린 후 결과를 사용할 수 있습니다.
- 하지만
await
를 사용하더라도 프로그램이 멈추지 않고 다른 작업은 계속해서 실행됩니다. 비동기 작업이 완료되면 결과 값을 사용할 수 있는 시점에 처리됩니다.
예시: Future의 상태 처리
// 비동기 함수에서 Future의 다양한 상태 처리
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return "데이터 로드 완료";
}
void main() {
print("데이터 로드 시작");
// Future 사용: 완료되면 처리
fetchData().then((result) {
print(result); // "데이터 로드 완료"가 출력됨
}).catchError((error) {
print("에러 발생: $error");
}).whenComplete(() {
print("데이터 로드 작업 완료");
});
print("다른 작업을 처리 중...");
}
위 코드는
Future
가 완료될 때 실행되는 콜백을 정의한 예시입니다. then
, catchError
, whenComplete
와 같은 메서드 체인을 사용하여 비동기 작업이 성공, 실패, 완료된 경우를 처리할 수 있습니다.5. Future<T>
의 장점
- 비동기 작업 처리:
Future
를 통해 비동기 작업의 결과를 관리하고, 완료될 때까지 기다리거나 그 결과를 쉽게 사용할 수 있습니다.
- 코드 가독성 향상:
async
와await
을 사용하면 복잡한 비동기 작업도 동기 코드처럼 읽기 쉽고 직관적으로 작성할 수 있습니다.
- 비동기 처리의 안정성:
Future
는 비동기 작업의 오류를 쉽게 관리할 수 있게 해주며, 비동기 작업이 완료된 후 안전하게 처리됩니다.
결론
비동기 함수는
Future<T>
타입의 결과를 반환하며, 비동기 작업이 완료되기 전까지 다른 작업을 중단하지 않고 처리할 수 있게 해줍니다. async
와 await
는 이러한 비동기 작업을 동기식 코드처럼 간단하게 작성할 수 있게 해주며, 비동기 함수의 결과는 Future<T>
를 통해 처리 및 관리됩니다.3. 비동기 함수의 유무에 따라 코드의 순차적 실행 방식
1. 동기 함수에서의 순차적 실행
동기 함수는 작업이 끝날 때까지 기다렸다가 다음 작업을 실행합니다. 즉, 하나의 작업이 완료되지 않으면 다음 작업은 시작되지 않습니다. 이는 순차적으로 실행된다는 의미입니다.
동기 함수 예시:
void main() {
print("작업 1 시작");
print("작업 2 시작");
print("작업 3 시작");
}
출력 결과:
작업 1 시작
작업 2 시작
작업 3 시작
위 예시에서는 모든 작업이 순차적으로 실행됩니다. 작업 1이 끝난 후에 작업 2가 시작되고, 그 후에 작업 3이 실행됩니다.
2. 비동기 함수에서의 순차적 실행
비동기 함수는 작업이 끝날 때까지 기다리지 않고 즉시 반환합니다. 이 때문에 비동기 작업은 순차적으로 실행되지 않고 병렬적으로 처리될 수 있습니다. 비동기 함수 내부에서
await
를 사용하면 그 함수가 완료되기를 기다리지만, 그렇지 않으면 작업이 끝나기 전에 다음 작업으로 넘어갑니다.비동기 함수 예시:
Future<void> asyncTask(int id) async {
await Future.delayed(Duration(seconds: 2));
print("작업 $id 완료");
}
void main() async {
print("작업 1 시작");
asyncTask(1); // 비동기 실행, 기다리지 않음
print("작업 2 시작");
asyncTask(2); // 비동기 실행, 기다리지 않음
print("작업 3 시작");
asyncTask(3); // 비동기 실행, 기다리지 않음
}
출력 결과:
작업 1 시작
작업 2 시작
작업 3 시작
작업 1 완료 (2초 뒤 출력)
작업 2 완료 (2초 뒤 출력)
작업 3 완료 (2초 뒤 출력)
위 코드는 비동기 함수인
asyncTask()
가 호출된 후에도 다음 작업들이 기다리지 않고 즉시 실행되는 것을 보여줍니다. asyncTask(1)
, asyncTask(2)
, asyncTask(3)
은 각각 비동기적으로 실행되며, 이 함수들에 있는 await Future.delayed
는 2초 뒤에 완료되므로 나중에 출력됩니다.3. 비동기 함수에서 순차적 실행 강제하기 (await
사용)
비동기 코드도
await
를 사용하면 특정 작업이 완료될 때까지 기다린 후 다음 작업을 실행할 수 있습니다. 이 경우, 순차적인 실행을 강제할 수 있습니다.await
로 순차적 실행 강제하기:
Future<void> asyncTask(int id) async {
await Future.delayed(Duration(seconds: 2));
print("작업 $id 완료");
}
void main() async {
print("작업 1 시작");
await asyncTask(1); // 작업 1이 완료될 때까지 대기
print("작업 2 시작");
await asyncTask(2); // 작업 2가 완료될 때까지 대기
print("작업 3 시작");
await asyncTask(3); // 작업 3이 완료될 때까지 대기
}
출력 결과:
작업 1 시작
작업 1 완료 (2초 뒤)
작업 2 시작
작업 2 완료 (2초 뒤)
작업 3 시작
작업 3 완료 (2초 뒤)
위 예시에서는
await
를 사용해 비동기 작업이 순차적으로 실행됩니다. asyncTask(1)
이 완료될 때까지 기다린 후에야 다음 작업인 asyncTask(2)
가 시작되고, 그 다음으로 asyncTask(3)
이 실행됩니다.4. 비동기 함수의 장점과 주의점
- 병렬 실행: 비동기 함수는 작업들이 병렬로 실행될 수 있도록 해주어, 오래 걸리는 작업들을 동시에 실행할 수 있습니다. 예를 들어, 네트워크 요청을 동시에 여러 개 보내거나 I/O 작업을 비동기적으로 처리할 수 있습니다.
- 순차적 실행 강제: 하지만 모든 작업이 병렬로 처리되길 원하는 것은 아니기 때문에, 순차적인 실행이 필요한 경우에는
await
를 사용하여 비동기 함수의 실행 순서를 제어할 수 있습니다.
5. 동기 vs 비동기 실행 요약
- 동기 실행: 작업이 완료될 때까지 기다렸다가 다음 작업을 실행함.
- 비동기 실행: 작업이 완료되기 전에 다음 작업을 바로 실행함.
await
를 사용하여 순차적으로 실행되도록 제어 가능.
비동기 함수는 기본적으로 병렬적으로 실행되지만,
await
키워드를 사용하면 순차적인 실행을 강제할 수 있습니다. 이를 통해 비동기 처리의 효율성을 극대화하거나, 필요에 따라 작업 순서를 유지하는 것이 가능합니다.4. Future코드 수업 예제
1. Future -ex01
// Future.delayed에 Duration객체에 3초를 설정해줬다. 통신 없이 3초 지연을 설정하고 이것을 마치 통신을 해서 3초 뒤에 받는다고 가정.
// 이 Future.delayed를 받은 Furture<int>는 3초 뒤 실행이라는 어음을 가지고 있다.
// 그렇기 때문에 이 어음을 처리하려면 처리하는 쪽에서 async(await를 품고 있는 함수의 소괄호와 중괄호 사이에 배치), await(딜레이가 있는 혹은 통신해서 값을 받아오는 함수 호출 앞)를 가지고 처리하면 된다.
// 처리한다는 게 무슨 의미냐면 아래 void start(){~}에서 받으면 void start(){~}에 async를 붙이고 값을 받아 오는데 시간이 걸리는 getNum() 호출 앞에 await붙여서 처리. 즉, async, await로 받아서 처리한다는 것이다.
// 이러면 main() 에서 start()를 실행하고 print('안녕')을 해두면 getNum()과 start()가 비동기로 처리되기 때문에 3초 뒤에 값을 가져오고 기다리는 사이에 안녕이 먼저 출력되고 3초 뒤에 n + 10이 출력된다.
// main에서 start();와 print('안녕');을 순차적으로 처리하려면 어떻게 해줘야 할까? -> ex02
Future<int> getNum() {
Future<int> fNum = Future.delayed(Duration(seconds: 3), () => 1);
return fNum;
}
void start() async {
int n = await getNum();
print(n + 10);
}
void main() {
start();
print('안녕');
}
2. Future -ex02
// main에서 안녕이 먼저 출력되지 않고 순차적으로 출력되려면 어떻게 해야될까?
// main()전체를 비동기로 처리하게 하면 된다.
// start()에 await를 붙여서 비동기 처리를 하면 main()에도 async를 붙여야 한다. 결과적으로 main() 전체가 비동기 함수가 된다.
// 그럼 main()전체가 비동기이기 때문에 await start()가 실행 되고 나서 print가 출력된다.
// 여기서 주의할 점이 start()는 리턴타입이 void인데
// 앞에 await가 붙으면 비동기 작업을 기다리는 것이므로, 이를 포함한 함수는 항상 async가 붙여야되고 async가 붙으면 Future<T> 타입을 반환해야 하지만
// void의 경우 리턴하는 값이 없기 때문에 void로 적어놔도 내부적으로 Future<void>로 리턴된다.
// 이렇게 main()이 전체 비동기 함수가 돼서 start()가 3초 뒤에 실행되기까지 기다렸다가 실행 후 그 다음에 안녕이 출력된다.
// 따라서 main() 내부에 동기,비동기가 혼재돼있을 경우 순차적으로 실행시킬지 말지는 main()자체가 비동기이냐(async) 아니냐에 따라 달라진다.
// 예를들어 main()이 로그인 시도이고 start()가 로그인 뒤 화면에 필요한 정보를 불러오는 것이고 print()가 화면이라면
// main()이 비동기가 되어야 시간이 걸리는 await start()를 기다렸다가 이후에 화면이 그려질 것이다.
// 만약 main()에 async가 없이 그냥 동기 처리를 한다면 start()가 값을 기다리는 동안 그 다음 코드인 화면이 그려지게 된다.
// 정보가 없는데 화면을 먼저 그리게 되면 필요한 정보를 못 받았는데 화면이 그려지므로 다 비거나 하기 때문에 순서를 고려한 적절한 동기 처리가 필요하다.
Future<int> getNum() {
Future<int> fNum = Future.delayed(Duration(seconds: 3), () => 1);
return fNum;
}
Future<void> start() async {
int n = await getNum();
print(n + 10);
}
void main() async {
await start();
print('안녕');
}
Share article