14. Flutter 수업- async, 비동기 , Future

async, 비동기 , Future
홍윤's avatar
Oct 09, 2024
14. Flutter 수업- async, 비동기 , Future
 

1. async, awiat와 비동기

💡
async, await비동기(Asynchronous) 처리 개념은 서로 밀접한 연관이 있으며, 비동기 프로그래밍에서 자주 사용됩니다. 이를 이해하기 위해 각각의 개념과 그 연관성을 설명해보겠습니다.

1. 비동기(Asynchronous) 처리란?

비동기 처리란 작업이 완료될 때까지 기다리지 않고 다른 작업을 계속 수행할 수 있는 방식입니다. 이 방식은 네트워크 요청, 파일 읽기/쓰기, 타이머 등 시간이 걸리는 작업을 할 때 유용합니다. 비동기 처리의 가장 큰 장점은 프로그램의 블로킹(blocking)을 방지하여 자원을 효율적으로 사용할 수 있다는 것입니다.

2. asyncawait

비동기 작업을 쉽게 관리하기 위해 asyncawait는 함수의 실행 흐름을 제어합니다.
  • 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("다음 작업 실행"); }

동기와 비동기의 차이

  1. 동기 처리: 동기 처리는 작업이 완료될 때까지 기다립니다. 모든 작업이 순차적으로 처리되기 때문에, 이전 작업이 끝나지 않으면 다음 작업을 진행할 수 없습니다.
      • 예: 전화 통화. 한 사람이 말을 할 때, 다른 사람은 기다려야 함.
  1. 비동기 처리: 비동기 처리는 작업이 끝날 때까지 기다리지 않고 다른 작업을 수행합니다. 결과가 준비되면 나중에 해당 결과를 처리합니다.
      • 예: 문자 메시지. 답장을 기다리지 않고 다른 일을 할 수 있음.

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를 통해 비동기 작업의 결과를 관리하고, 완료될 때까지 기다리거나 그 결과를 쉽게 사용할 수 있습니다.
  • 코드 가독성 향상: asyncawait을 사용하면 복잡한 비동기 작업도 동기 코드처럼 읽기 쉽고 직관적으로 작성할 수 있습니다.
  • 비동기 처리의 안정성: Future는 비동기 작업의 오류를 쉽게 관리할 수 있게 해주며, 비동기 작업이 완료된 후 안전하게 처리됩니다.

결론

비동기 함수는 Future<T> 타입의 결과를 반환하며, 비동기 작업이 완료되기 전까지 다른 작업을 중단하지 않고 처리할 수 있게 해줍니다. asyncawait는 이러한 비동기 작업을 동기식 코드처럼 간단하게 작성할 수 있게 해주며, 비동기 함수의 결과는 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

Uni