비동기 프로그래밍 (Asynchronous Programming)

박민주·2022년 7월 24일
0

Unity

목록 보기
31/40

동기 프로그래밍 (Synchronous Programming)

  • 프로그램이 실행되는 도중 어떤 작업을 요청하면, 그 작업이 종료될 때까지 기다렸다가 다음 작업을 하는 것
  • 동기 방식은 요청과 결과가 동시에 일어나기 때문에 설계가 매우 간단하고 직관적이지만 작업이 완료될 때까지 프로그램이 대기해야 함

비동기 프로그래밍 (Asynchronous Programming)

  • 프로그램이 실행되는 도중 어떤 작업을 요청한 뒤 그 결과는 기다리지 않고 다음 동작을 함
  • 동기 프로그래밍에 비해 설계 단계에서 더 복잡하고, 요청한 작업이 완료될 때 별도로 확인해야 함
  • 요청한 작업이 오래 걸리더라도 다른 작업을 계속할 수 있기 때문에 자원을 효율적으로 사용할 수 있음

Task 클래스

  • 일반적으로 하나의 프로세스는 하나의 스레드를 가지고 작업을 수행한다
  • 하나의 프로세스에서 둘 이상의 스레드가 동시에 작업을 수행하도록 하려면 멀티 스레드를 이용해야 한다
  • Task 클래스는 .Net 4.0부터 도입된 클래스로 멀티 스레드에서 비동기 작업을 실행한다
  • 태스크를 사용하는 방법은 직접 호출, Action 대리자 사용, 대리자, 람다식, 람다와 익명 메서드가 있다

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.Threading.Tasks; // Task

public class TaskExample : MonoBehaviour
{
    /* Task Method
     * Factory.StartNew(): 스레드를 생성과 동시에 시작
     * Start(): Task 스레드 시작
     * Wait(): Task 끝날 때까지 대기
     * **/
    void Start()
    {
        // 직접 호출, 스레드 생성 및 시작
        Task.Factory.StartNew( () => { Debug.Log( "Task" ); } );

        // Action
        Task task2 = new Task( new System.Action( DebugLog ) );
        task2.Start();

        // delegate 대리자 
        Task task3 = new Task( delegate { DebugLog(); } );
        task3.Start();

        // 람다식 
        Task task4 = new Task( () => DebugLog() );
        task4.Start();

        // 람다와 익명 메서드
        Task task5 = new Task( () => { DebugLog(); });
        task5.Start();

        // 각 Task가 끝날 때까지 대기
        // 대기하지 않으면 Main이 먼저 끝나서 결과를 보지 못함
        task2.Wait();
        task3.Wait();
        task4.Wait();
        task5.Wait();
    }

    void DebugLog()
    {
        Debug.Log("Task");
    }
}

Task` 클래스

  • 일반적인 Task 클래스는 리턴값을 받지 못한다
  • 리턴값을 받으려면 Generic 클래스를 사용해야 한다
  • Result 속성를 통해 리턴값을 얻어올 수 있는데, 이 때 아직 스레드가 실행중이면 끝날 때까지 대기한다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.Threading.Tasks; // Task

public class GenericTaskExample : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Task<int> intTask = Task.Factory.StartNew<int>( () => GetSize( "GenericTask " ) );

        /*
         * 메인 스레드에서 다른 작업 실행
         */

        int result = intTask.Result;
        Debug.Log( result );
    }

    int GetSize( string data )
    {
        return data.Length;
    }
}

위 경우는 Task에서 리턴값을 꼭 받아야 하는데
그 작업이 무거울 경우 /메인 스레드에서 다른 작업 실행/ 을 활용하여
메인 스레드에게 다른 일을 시키면 좋다고 한다.

Task 작업 취소하기

  • CancellationToken 을 이용하여 Task를 취소할 수 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.Threading;
using System.Threading.Tasks;

public class CancellationTokenExample : MonoBehaviour
{
    // 1. 클래스 멤버로 CancellationTokenSource 선언
    CancellationTokenSource m_CancelTokenSource;
    Task<int> m_Task;
    
    void Start()
    {
        // 2. CancellationTokenSource 객체 생성
        m_CancelTokenSource = new CancellationTokenSource();

        CancellationToken cancellationToken = m_CancelTokenSource.Token;
        m_Task = Task.Factory.StartNew(TaskMethod, cancellationToken);
    }

    
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.C))
        {
            // CancellationTokenSource 의 Cancel() 를 통해 작업 취소

            m_CancelTokenSource.Cancel();

            if(m_Task != null)
            {
                Debug.Log( $"Count: {m_Task.Result}" );
            }
        }
    }

    private int TaskMethod()
    {
        int count = 0;
        for (int i = 0; i < 10; i++)
        {
            // 비동기 작업 메서드 안에서 작업이 취소되었는지 체크
            if (m_CancelTokenSource.Token.IsCancellationRequested)
            {
                break;
            }

            ++count;
            Thread.Sleep( 1000 );
        }
        return count;
    }
}

Await, Async

  • C# 5.0 부터 추가된 키워드 async과 await은 비동기 프로그래밍을 지원하는 키워드이다
  • async는 해당 메서드가 await을 가지고 있음을 알려준다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.Threading;
using System.Threading.Tasks;

public class AsyncAwaitExample : MonoBehaviour
{
    /* Async, Await Method
     * Run(): 비동기 시작
     * FromResult(): 비동기 시작 후 결과값을 얻음 */

    void Start()
    {
        TaskRun();
        TaskFromResult();
    }

    async void TaskRun()
    {
        var task = Task.Run( () => TaskRunMethod( 3 ) );
        int count = await task;
        Debug.Log( $"Count: {task.Result}" );
    }

    private int TaskRunMethod(int limit)
    {
        int count = 0;
        for (int i = 0; i < limit; i++)
        {
            ++count;
            Thread.Sleep( 1000 );
        }

        return count;
    }

    async void TaskFromResult()
    {
        int sum = await Task.FromResult( Add(1, 2) );
        Debug.Log( sum );
    }

    private int Add(int a, int b)
    {
        return a + b;
    }
}

참고

profile
Game Programmer

0개의 댓글