Application/Flutter

[Flutter] Dart의 비동기 처리 방식과 스레드

유제필 2022. 11. 21. 20:20

다트는 비동기 처리를 지원하는 언어이다.

 

비동기(Asynchronous)란

 

비동기란 언제 끝날지 모르는 작업을 기다리지 않고 다음 작업을 처리하게 해주는 것을 의미한다.

 

만약 비동기를 지원하지 않고 동기(Synchronous)로만 처리한다면, 어떤 작업이 오래 걸릴 경우 사용자는 실행이 멈춘 것으로 생각할 수 있다.

 

일반적으로 네트워크에서 데이터를 가져오거나 데이터베이스에 쓰기, 파일 읽기 등의 작업은 상황에 따라 언제 끝날지 모르므로 비동기로 처리해야 한다.

 

아래 그림은 동기와 비동기 방식을 표현한 이미지이다.

동기 방식은 한 작업이 끝나면 다음 작업을 하고, 비동기 방식은 동시에 진행되는 것을 볼 수 있다.

 

 

Dart의 비동기 프로세스 작동 방식

 

다트 언어에서는 async와 await 키워드를 이용해 비동기 처리를 구현한다.

처리 방법은 크게 3가지로 구현한다.

 

1. 함수 이름 뒤 본문이 시작하는 중괄호 { 앞에 async 키워드를 붙여 비동기로 만든다.

2. 비동기 함수 안에서 언제 끝날지 모르는 작업 앞에 await 키워드를 붙인다.

3. 2번 작업을 마친 결과를 받기 위해 비동기 함수 이름 앞에 Future(값이 여러 개일 경우 Steram) 클래스를 지정한다.

 

void main()
{
    checkVersion();
    print('end process');
}

Future checkVersion async {
    var version = await lookUpVersion();
    print(version);
}

int lookUpVersion() {
    return 12;
}

 

실행 결과

end process
12

 

main 함수에서 제일 먼저 checkVersion 함수를 호출하였고 안에 lookUpVersion 함수가 호출된다.

일반적인 생각으로 checkVersion 함수가 먼저 호출되므로 12가 먼저 출력이 될거라 생각할 수 있지만, 실제 실행 결과는 12가 먼저 출력되는게 아닌 end process가 먼저 출력 되었다.

 

이 이유는 checkVersion 함수 이름 앞 뒤로 Future와 async가 붙어 있어서 그렇다.

즉, 함수를 비동기로 만들어서 이런 현상이 발생한다.

 

checkVersion 함수 안에 await 붙은 함수를 따로(비동기 처리) 처리한 다음, 그 결과는 Future 클래스에 저장해 둘 테니, checkVersion 함수 외 나머지 코드를 실행해라 라는 의미이다.

 

lookUpVersion 앞에 await 키워드가 붙었는데, await 키워드는 처리를 완료하고 결과를 반환할 때까지 이후 코드의 처리를 멈춘다.

 

따라서 loopUpVersion 함수를 호출해 version 변수에 12가 저장된 다음에야 print(version)으로 12를 출력한다.

 

이처럼 비동기 함수에서 어떤 결괏값이 필요하다면 해당 코드를 await으로 지정한다.

이렇게 하면 네트워크 지연 등으로 제대로 된 값을 반환받지 못한 채 이후 과정이 실행되는 것을 방지할 수 있다.

 

비동기 함수 반환 값 활용 then() 함수

 

비동기 함수가 반환하는 값을 처리하려면 then 함수를 이용한다.

 

void main() async
{
    await getVersionName().then((value) => {
        print(value)
    });
    
    print('end process');
}

Future<String> getVersionName() async {
    var versionName = await lookUpVersionName();
    print(versionName);
}

String lookUpVersion() {
    return 'Android Q';
}


실행 결과

Android Q
end process

 

Future<String> 이라는 반환 값을 정해 놓은 getVersionName() 함수가 있다.

이 함수는 async 키워드가 붙었으므로 비동기 함수이다.

 

이처럼 비동기 함수가 데이털르 성공적으로 반환하면 호출하는 쪽에서 then 함수를 이용해 처리한다.

then 함수로 반환받은 값을 print로 출력 시 Android Q가 출력되는 것을 볼 수 있다.

 

then 함수 이외에도 error 함수를 이용해 출력할 수 있다.

 

다트의 스레드(Thread)

 

다트는 하나의 스레드로 동작하는 단일 스레드 프로그래밍 언어이다.

그래서 다트에서 멀티 프로그래밍을 하기 위해 await 키워드를 사용해서 작성하여야 한다.

 

아래 예제는 await 키워드를 이용해 멀티 스레드를 적용한 예제이다.

 

void main() {
   printOne();
   printTwo();
   printThree();
}
 
void printOne() {
    print('One');
}
 
void printThree() {
    print('Three');
}
 
void printTwo() async {
    Future.delayed(Duration(seconds: 1), () { 
        print("Future!!");
    });
    print('Two');
}

 

실행 결과

One
Two
Three
Future!!

 

Future.delayed 함수는 Duration 기간 동안 기다린 후에 진행하라는 의미로, 1초 후에 진행하라는 의미이다.

 

 

One 출력 후 printTwo 함수에 진입하면서 Future를 1초 지연했으므로 async로 정의한 비동기 함수의 특징에 따라 Two가 먼저 출력된다.

 

그리고 Three를 출력하고 Future!!가 가장 늦게 출력된다.

 

프로그래밍의 순서대로 하면 Future가 출력되어야 하지만 Delay를 주었으므로 가장 늦게 출력되는 것을 볼 수 있다.

 

하지만 여기서 Future 코드 앞에 await 키워드를 붙이면 다른 결과가 나오게 된다.

 

void main() {
   printOne();
   printTwo();
   printThree();
}
 
void printOne() {
    print('One');
}
 
void printThree() {
    print('Three');
}
 
void printTwo() async {
    await Future.delayed(Duration(seconds: 2), () { 
        print("Future Method");
    });
    print('Two');
}

 

실행 결과

One
Three
Future Method
Two

 

await 키워드를 붙였더니 Future Mehod 로그가 먼저 출력되었다.

이처럼 await 키워드를 이용하면 await 키워드가 속한 함수의 프로세스가 끝날 때까지 기다리기 때문에 이를 잘 생각해서 프로래밍을 해야 한다.

 

 

출처 : Do it! 플러터 앱 프로그래밍