포스트

Task에 대해서

Task 클래스

  • Thread, Thread Pool의 단점을 개선한 클래스
  • Task는 Background Thread이며 Thread Pool을 이용한다. - 예제 1
  • Task의 생성에 람다식을 사용할 수 있다. - 예제 2
  • Thread의 Join() 함수와 유사한 Wait() 함수를 지원한다. - 예제 2, 3
  • ContinueWith() 함수를 통해 Task가 완료된 후에 실행할 Task를 지정할 수 있다. - 예제 4
  • Run() 함수를 통해 Task의 생성과 시작을 한번에 실행할 수 있다. - 예제 5
  • WhenAll() 함수를 통해 여러 Task들이 모두 완료될 때까지 대기한 후, 모두 완료되면 새로운 Task를 반환할 수 있다. - 예제 5
  • WhenAny() 함수를 통해 여러 Task 중 가장 먼저 완료된 Task를 다루는 것도 가능하다. - 예제 6
  • Run() 함수보다 더 많은 옵션과 기능을 제공하는 Factory.StartNew() 도 존재한다. - 예제 7
  • Task<>Func<>처럼, Task의 실행 결과로 값을 반환할 수 있다. - 예제 8

예제 1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class TestTask : MonoBehaviour
{
    void Start()
    {
        Task task = new Task(BackgroundTask);
        task.Start();
    }

    void BackgroundTask()
    {
        bool isBackground = Thread.CurrentThread.IsBackground;
        Debug.Log($"백그라운드 스레드 : {isBackground}");

        bool isThreadPoolThread = Thread.CurrentThread.IsThreadPoolThread;
        Debug.Log($"스레드 풀 : {isThreadPoolThread}");
    }
   
}

위 코드를 실행시켰을 때 결과는 모두 true로 출력된다.

즉, 현재 스레드는 Background Thread이고, Thread Pool에 속하는 스레드라는 것이다.

위 코드는 Task를 통해 실행된다.

이를 통해 Task는 Background Thread이며 Thread Pool을 이용한다는 것을 알 수 있다.

예제 2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestTask : MonoBehaviour
{
    private void Start()
    {
        ThreadPool.QueueUserWorkItem((obj) =>
        {
            Debug.Log("ThreadPool 실행");
        });

        Task task = new Task(() =>
        {
            Debug.Log("Task 실행");
        });

        task.Start();
        task.Wait();

    }
}

Task의 생성자로 action을 받기 때문에, 위 코드와 같이 람다식을 활용할 수도 있겠다.

Wait() 함수로 Thread의 Join() 함수와 유사한 기능을 지원한다.

예제 3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestTask : MonoBehaviour
{
    private void Start()
    {
        Task task = new Task(SleepAction);
        task.Start();
        task.Wait();        // task가 완료될 때까지 현재 스레드 대기
        Debug.Log("Task 완료");
    }

    void SleepAction()
    {
        Thread.Sleep(3000);
    }
}

위 코드를 실행하면, 3초가 지난 후에 “Task 완료” 라는 문구가 콘솔창에 출력된다.

Wait() 함수는 이렇게 활용할 수 있겠다.

예제 4.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TestTask : MonoBehaviour
{
    private void Start()
    {
        Task task = new Task(SleepAction);
        // Task가 완료된 후 수행할 작업 지정
        task.ContinueWith((obj) =>
        {
            Debug.Log("Task 완료");
        });
        
        task.Start();
    }

    void SleepAction()
    {
        Thread.Sleep(3000);
    }
}

이는 예제 3과 겉으로는 동일한 기능을 하지만, ContinueWith() 함수를 사용한다.

ContinueWith() 함수는 Task가 완료된 후 실행할 Task를 지정한다.

기존의 Thread는 이러한 기능을 지원하지 않는다.

예제 5.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class TestTask : MonoBehaviour
{
    private Task task1, task2, task3;
    private Stopwatch stopwatch;
    
    private void Start()
    {
        stopwatch = new Stopwatch();
        stopwatch.Start();

        task1 = Task.Run(Task1Function);
        task2 = Task.Run(() =>
        {
            Thread.Sleep(2000);
        });

        task3 = Task.WhenAll(task1, task2);
    }

    private void Update()
    {
        if (task3.IsCompleted)
        {
            stopwatch.Stop();
            UnityEngine.Debug.Log("모든 작업이 완료되었습니다.");
            UnityEngine.Debug.Log($"경과 시간 : {stopwatch.Elapsed.TotalSeconds}초");
        }
    }

    void Task1Function()
    {
        Thread.Sleep(3000);
    }
}

  • Run() : Task의 생성과 시작을 한번에 하는 메서드
  • WhenAll() : Task가 모두 완료될 때까지 대기하고, 모두 완료되면 새로운 Task를 반환하는 메서드.

위 코드는 StopWatch를 활용해 task1, task2, task3가 모두 완료되는 데 걸리는 시간을 측정한다.

예제 6.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class TestTask : MonoBehaviour
{
    private Task task1, task2, task3;
    private Stopwatch stopwatch;
    
    private void Start()
    {
        stopwatch = new Stopwatch();
        stopwatch.Start();

        task1 = Task.Run(Task1Function);
        task2 = Task.Run(() =>
        {
            Thread.Sleep(2000);
        });

        task3 = Task.WhenAny(task1, task2);
    }

    private void Update()
    {
        if (task3.IsCompleted)
        {
            stopwatch.Stop();
            UnityEngine.Debug.Log("모든 작업이 완료되었습니다.");
            UnityEngine.Debug.Log($"경과 시간 : {stopwatch.Elapsed.TotalSeconds}초");
        }
    }

    void Task1Function()
    {
        Thread.Sleep(3000);
    }
}

  • WhenAny() : 매개변수 Task 중 먼저 완료된 Task를 반환한다.

task1보다 task2가 먼저 끝날 것이기 때문에, Task.WhenAny(task1, task2); 의 결과로 task2가 반환될 것이다.

예제 7.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestTask : MonoBehaviour
{
    private void Start()
    {
        Task task = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(3000);
        });

        task.Wait();
        Debug.Log("완료");
    }
}
  • Task.Factory.StartNew() : Run() 함수와 유사하게 새로운 작업(Task)을 생성하고 실행하는 데 사용되는 메서드다.
    • Run() 함수보다 더 많은 제어와 옵션을 제공하지만, 그만큼 복잡할 수 있다.

예제 8.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestTask : MonoBehaviour
{
    private void Start()
    {
        Task<int> task1 = new Task<int>(() => 1);
        Task<float> task2 = Task.Run(() => 2.0f);
        Task<long> task3 = Task.Factory.StartNew(() => 3L);

        task1.Start();

        task1.Wait();
        task2.Wait();
        task3.Wait();
        
        Debug.Log(task1.Result);
        Debug.Log(task2.Result);
        Debug.Log(task3.Result);
    }
}

위 코드의 실행결과는 1, 2, 3이 각각 콘솔창에 출력된다.

Func<>처럼, Task의 실행 결과로 반환값이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestTask : MonoBehaviour
{
    private void Start()
    {
        Task<int> task = Task.Run(Test);
        task.Wait();
        Debug.Log($"실행 결과 : {task.Result}");
    }

    int Test()
    {
        int result = 0;
        for (int i = 0; i < 10; i++)
        {
            Thread.Sleep(100);
            result = i + 1;
            Debug.Log($"반복 횟수 : {result}");
        }

        return result;
    }
}

위 코드의 실행결과는 다음과 같다.

image (19)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.