본문 바로가기
유니티 최고/유니티 개념

유니티(Unity) async await이란 & 동기식, 비동기식 작업

by Lee_story_.. 2023. 6. 1.
728x90

유니티에서 라이브러리를 사용하다 보면 가끔 

async await  >> 이런아이들을 볼 수 있습니다.

 

 

이번 글은 이것들이 어떤 역할을 하는지 알아보도록 하겠습니다!

 

 

 

둘을 알기 전에 먼저 알아둬야 할 것들이 있습니다. 

바로 동기식과 비동기식 작업의 차이입니다. 

 

 


 

동기식?

동기식 작업이란 순차적인 실행 방식을 설명합니다. 만약 2가지의 함수가 있다면 이것들은 차례에 따라 실행 된다는 것!

유니티는 일반적으로 동기식 작업 방식을 채택하여 프로젝트를 실행합니다.  

 

동기식 작업방식은 코드를 구현하기가 쉽고 실행 순서를 빠르게 파악할 수있습니다.

하지만 하나의 함수에서 엄청난 자원과 시간을 소모한다면 프로그램에 치명적일 수 있습니다. 

 

그렇기에 이러한 부분에서는 비동기식으로 코드를 구성하기도 합니다! 

 

 

 

 

비동기식?

비동기식 작업은 동기식과는 다르게 여러작업들을 동시에 실행하는 방식입니다.

이러한 점이 앞에서 말했던 동기식 작업의 단점을 쉽게 극복해줍니다.

 

하지만 비동기식 작업은 구현이 복잡하며, 구조를 정확하게 파악하지 않고 구현할 시에는 무한 루프, 무한 대기 상태에 빠지는 경우도 있기에 조심하여 구현해야합니다.

 

 


 

대부분의 코드상에서는 동기식으로 작성해도 문제가 없습니다.

하지만 데이터베이스 , 웹 서비스를 불러오는 과정 등

실행 시간의 길이나 항시 실행 해야하는 처리과정에선  비동기식 작업방식은 꼭 필요한 존재입니다. 

 

 

 

 

유니티에서 비동기식 작업 방식을 구현하는 방법은 3가지가 있습니다.  

 

1. 코루틴 사용 <비동기식 같은 동기식 작업....>

     >>  코루틴이라는 요소 또한 비동기식 작업 방식을 구현 할 수 있지만 코루틴은 동기식 방식을 이용하여 비동기식 방식을 모방하는 것으로 완전히 같은 효율을 보여주지 않습니다.

 

2. async / await 

   >> 동기식 작업방식인 유니티에서도 비동기식 작업 방식을 구현 하기위한 async / await 를 지원하고 있습니다. 

 

3. Task.Run(() => 함수~());

  >> task.run 함수를 이용하면 내부의 함수를 비동기적으로 바로 실행 할 수 있습니다. 

 

더 있다면 추후에 추가 하겠습니다.

 

 

코루틴에 대해서 알아보고싶다면 이전글!

 

유니티(Unity) 코루틴(Coroutine) 이란 & 사용해보기

이번엔 코루틴이란 개념에 대해 정리/사용해보겠습니다. 코루틴이란? 함수를 시간에 따른 지연처리를 하기 위해 사용하는 기능으로 유니티에서 제공하는 주요한 기능입니다. 특정 상황에 대해

ljhyunstory.tistory.com

 

 

 

 

 

그럼이제 async / await를 알아보겠습니다.


async / await?

 

async -  비동기식 (Asynchronous) 의 줄임말로

메서드 앞에 async라고 지정해주면 비동기 함수를 만들 수 있습니다. 

public async Task FirstTestAsync()

Task?  >>>  작업의 단위를 반환해주는 변수로 결과값은 그 작업의 결과를 제공해줍니다!

 

ex ) Task    >> void 형 반환값 x    ,   Task<int>   >>> int형 반환

 

 

 

비동기함수의 내부는 일반함수와 비슷합니다.

한가지 추가된 점이 있다면 awit

 async Task testc()
    {
       await ~~~~ ;
       
       ~~~
        
    }

 

await - 작업을 기다린다는 의미의 키워드로 await내부의 코드가 끝날때까지 그 지점에서 정지됩니다.

 

꼭 사용하지 않아도 되는 요소이지만 비동기 함수를 사용하는 이유를 생각해보면 왜 사용해야하는지 알 수 있습니다.

 

 

현재 로그인을 하고 있고 로딩창을 돌림과 동시에 id를 데이터 베이스에서 들고오는 상황이라 가정합시다.

만약 await를 사용하지 않는다면 id를 들고 오지도 않았는데 다음 함수들을 실행하는 경우가 발생합니다. 

 

 

물론 코드상으로 무한 반복하여 id의 여부를 체크할 수 있지만 비동기 작업을 사용하는 이유가 없기에 

대부분은 await 키워드를 사용하여 작업의 여부를 대기해줍니다. 

 

 

 

>>>> 그러면 await 만큼 기다리니까 동기식이랑 다를바가 없지않나???

   >>>>> 일부 맞습니다. 

async는 유니티 전체 프로그램과 별개로 작동(비동기) 하는것이지 코드 내부는 동기식으로 작동하게됩니다.

 

 

 

 

그럼 이번엔 Task.Run에 대해 알아보겠습니다.


Task.Run(() => 함수~());

using System.Threading.Tasks; 라이브러리를 이용하여 구현하는 방법으로 

매우간단합니다.

 

아래처럼 구성하고 각 키를 누르게되면

void Update()
    {
        print("CCCC");
        if (Input.GetKeyUp(KeyCode.K))  //비동기 실행
        {
            print("비동기-------------------------------------");
            Task.Run(() => SeconTest());
        }
         if (Input.GetKeyUp(KeyCode.S))  // 동기 
        {
            print("동기-------------------------------------");
            SeconTest();
        }
        
....
public void SeconTest()
    {
        for (int i = 0; i < 50; i++)
        {
            print("DDD-------------");
        }
    }

 

아래처럼 출력결과가 나오는데

CCCC와 DDD가 동시에 출력되는지 따로 출력되는지를 보고 동기, 비동기를 확인하실 수 있습니다.

 

Task.Run을 이용하면 간단하게

async Task testc() 같은 비동기식 함수, 일반적인 동기식 함수 모두 비동기식으로 처리 가능합니다.

 

 

 

하지만 async/await를 사용하는 것이 좀 더 명확한 방법이며

앞서 말한 await의 역할 또한 무시하지 못합니다...

그렇기에 그 함수의 역할에 따라 async, Task.Run 을 골라 사용하시면 될 것 같습니다. 

 

 

 

여기까지.... 개념은 끝났지만 저에겐 어려운 부분이라 몇가지 예제를 더 만들어 보았습니다.


예제

void Update()
{
 	if (Input.GetKeyDown(KeyCode.Space))
        {
            print("비동기시작-------------------------------------");
            Task.Run(() => testc());
            print("스페이스끝!-------------------------------------");
        }
       

        print("CCCC");
    }

async Task testc()
    {
        int temp = await AsyncNum();
        print(temp);
        print("비동기진짜끝-------------------------------------"); 
    }


    async Task<int> AsyncNum()
    {
        
        await Task.Delay(50);
        // 결과 값 반환
        return 7;
    }

 

1. Task<int>의 값 처리입니다. 


 

이런식으로 출력됩니다. 

 

2. await의 역할에 대해서 극단적으로 코드를 구성해 보았습니다

 

int temp = 0;

void Update()
{
 	if (Input.GetKeyDown(KeyCode.Space))
        {
            print("비동기시작-------------------------------------");
            Task.Run(() => testc());
            Task.Run(() => AsyncNum());

            print("스페이스끝!-------------------------------------");
        }
        print("CCCC");
    }

    async Task testc()
    {
        print(temp);
        print("비동기진짜끝-------------------------------------"); 
    }
    
    async Task AsyncNum()
    {
        for(int i = 0; i < 1000000000; i++)
        {
            temp++;
        }
    }

 

위 처럼 await 코드 없이 실행하면 당연히 

비동기적으로 실행하기에 temp는 초기값 0이출력될 수 밖에 없습니다....

 

 

이런경우에는 

 

따로 비동기적 함수를 만들어 관리 함으로써 해결할 수 있었습니다. 

async Task addMethod()
    {
        await AsyncNum();
        await testc();
    }

 

 

 

이제 마무리!


매우 생소하고 일반적인 프로그램 수준에선 사용할 일이 없겠지만 

비동기 작업 방식을 어떻게 구현하는지는 알아두는 것이 좋을 것 같습니다. 

 

진짜 마지막으로 얼마 사용안해 보긴 했지만 몇가지 알게된점을 정리해보도록하겠습니다.

 

 

1. async = 비동기식으로 선언한 함수 안에서만 await를 사용할 수 있다. 

 

2. async Task<int> 처럼 리턴값이 있다면 .Result 로 결과값을 받아올 수 있다.

int temp = AsyncNum().Result  
int temp = await AsyncNum()

result는 작업이 끝날때까지 다른 작업을 블로킹  / await는 작업이 끝날때 까지 기다리면서 다른 작업 또한 수행

차이가 좀 크네요...

 

 

3. Task.Run 이 만능이긴하나 await부분을 구현하느니 async형식으로 구성하는것이 나아 보인다.

 

 

 

일단 여기까지 더 추가되거나 수정될 사항이 있다면 바로 돌아오겠습니다.

 

 

 

 

 

틀린점이 있다면 댓 달아주세요!

 

댓글