■ 동기 처리란?

앞 처리의 결과가 반환돼야 다음 처리로 넘어가는 것

ex) 요리하기

 

■ 비동기 처리란?

앞 처리의 결과를 기다리지 않고 다음 처리로 넘어가는 것

ex) 요리하면서 욕실에 물 받기

 

■ 비동기 처리의 종류

Thread, Task, async, await

 

■ 옛 방식의 비동기 처리 「Thread」

아래는 메인 스레드에서는 HeavyMethod2를, 다른 별도의 스레드에서 HeavyMethod1를 비동기로 실행하는 소스이다.

Program
{
    static void Main(String[] args)
    {
    	Thread thread = new Thread(new ThreadStart(() =>
        {
        	HeavyMethod1();
        }));
        
        thread.Start();
        
        HeavyMethod2();
        
        Console.ReadLine();
    }
    
    static void HeavyMethod1()
    {
    	Console.WriteLine("매우 무거운 처리1 시작");
        Thread.Sleep(5000);
        Console.WriteLine("매우 무거운 처리1 종료");
    }
    
    static void HeavyMethod2()
    {
    	Console.WriteLine("매우 무거운 처리2 시작");
        Thread.Sleep(3000);
        Console.WriteLine("매우 무거운 처리2 종료");
    }
}

결과

매우 무거운 처리2 시작
매우 무거운 처리1 시작
매우 무거운 처리2 종료
매우 무거운 처리1 종료

■ 비동기 처리 「Task」

Task는 Thread에서는 할 수 없는, 다음 세 가지를 실현할 수 있다.

- 비동기로 실시한 처리의 상태(실행중, 완료, 취소, 에러)를 알 수 있다.

- 예외를 보충(처리 할 수 있다는 말인가?)할 수 있다.

- 비동기 처리의 실행순서를 제어할 수 있다.

Program
{
    static void Main(string[] args)
    {
    	// Task를 생성한다.
        // Task.Run메소드는 Task를 생성하는 Factory 클래스로, Task형의 객체를 반환한다.
        // 인수에는 delegate를 전달한다.
        // 단순히 HeavyMethod1을 실행하고 싶은 경우, 인수가 없는 delegate를 전달한다.
        Task task = Task.Run(() => {
        	HeavyMethod1();
        });
        
        HeavyMethod2();
        
        Console.ReadLine();
    }
    
    static void HeavyMethod1()
    {
    	Console.WriteLine("매우 무거운 처리1 시작");
        Thread.Sleep(5000);
        Console.WriteLine("매우 무거운 처리1 종료");
    }
    
    static void HeavyMethod2()
    {
    	Console.WriteLine("매우 무거운 처리2 시작");
        Thread.Sleep(3000);
        Console.WriteLine("매우 무거운 처리2 종료");
    }
}

결과

매우 무거운 처리2 시작
매우 무거운 처리1 시작
매우 무거운 처리2 종료
매우 무거운 처리1 종료

■ Thread와 Task 비교 ~1~ 「결과값을 취득하고 싶을 때」

・Thread의 경우

Program
{
    static void Main(string[] args)
    {
    	// 메인 스레드에서 접근할 수 있는 변수를 정의하고 이 곳에 결과를 저장한다.
        string result = "";
        
        Thread thread = new Thread(new ThreadStart(() =>
        {
            // 메인스레드와 다른 스레드로 HeavyMethod를 실행하여,
            // 결과(이러쿵저러쿵)을 result에 저장한다.
            result = HeavyMethod();
        }));
        
        // 스레드를 시작한다.
        thread.Start();
        
        // Join 메소드를 사용하면 스레드가 종료할 때까지 다음 처리를 실행하지 않는다.
        thread.Join();
        
        // 결과를 콘솔에 표시한다.
        Console.WriteLine(result);
        
        Console.ReadLine();
    }
    
    static string HeavyMethod()
    {
    	Console.WriteLine("매우 무거운 처리 시작");
        Thrad.Sleep(5000);
        Console.WriteLine("매우 무거운 처리 종료");
        
        return "이러쿵저러쿵";
    }
}

결과

매우 무거운 처리 시작
매우 무거운 처리 종료
이러쿵저러쿵

・Task의 경우

Program
{
    static void Main(string[] args)
    {
    	// 결과를 취득하는 경우 Task를 Generic 타입으로 정의하여 그 Generic에 반환값의 타입을 지정한다.
        Task<string> task = Task.Run(() =>
        {
            return HeavyMethod();
        });
        
        // 결과를 취득한다.
        // Result의 속성을 사용하면 Task에서 지정한 HeavyMethod가 종료할 때까지 대기하며,
        // 결과를 result 변수에 대입해준다.
        string result = task.Result;
        
        // 결과를 콘솔에 표시한다.
        Console.WriteLine(result);
        
        Console.ReadLine();
    }
    
    static string HeavyMethod()
    {
    	Console.WriteLine("매우 무거운 처리 시작");
        Thrad.Sleep(5000);
        Console.WriteLine("매우 무거운 처리 종료");
        
        return "이러쿵저러쿵";
    }
}

결과

매우 무거운 처리 시작
매우 무거운 처리 종료
이러쿵저러쿵

Task의 Result 속성을 사용하면, Thread에 비해 직관적이고 간단하게 결과값을 얻을 수 있다.

Result 속성의 사용으로 Thread에서 수행하던 다음 2가지 처리를 하지 않아도 된다.

- 메인스레드에 result 변수를 미리 정의하는 것

- 스레드가 종료할 때까지 Join 메소드로 대기하는 것

→ Task는 개발자가 스레드를 의식하지 않고 동기 처리같은 코드로 비동기 처리를 실현하게 해준다.

 

■ Thread와 Task 비교 ~2~ 「예외를 보충하고 싶을 때」

・Thread의 경우

 

Program
{
    static void Main(string[] args)
    {
    	try {
            Thread thread = new Thread(new ThreadStart(() =>
            {
            	throw new Exception("받아줘 >_<");
                HeavyMethod();
            }));
            
            thread.Start();
            
        } catch (Exception e) {
            Console.WriteLine("받아줘~ Catch!!")
        }
        
        Console.WriteLine("종료");
        
        Console.ReadLine();
    }
    
    static void HeavyMethod()
    {
    	Console.WriteLine("매우 무거운 처리 시작");
        Thread.Sleep(5000);
        Console.WriteLine("매우 무거운 처리 종료");
    }
}

결과

종료

스레드 안의 예외를 처리하지 않는다.

 

・Task의 경우

Program
{
    static void Main(string[] args)
    {
        Task task = Task.Factory.StartNew(() => {
            throw new Exception("받아줘 >_<");
            HeavyMethod();
        });
        
    	try {
            // 예외를 캐치하는 경우, Wait 메소드를 실시하는 부분을 try...catch로 감싼다.
            // 예외가 발생하면 AggregateException에 Wrap되어 Throw된다.
            task.Wait();
        } catch (Exception e) {
            Console.WriteLine("받아줘~ Catch!!")
        }
        
        Console.WriteLine("종료");
        
        Console.ReadLine();
    }
    
    static void HeavyMethod()
    {
    	Console.WriteLine("매우 무거운 처리 시작");
        Thread.Sleep(5000);
        Console.WriteLine("매우 무거운 처리 종료");
    }
}

결과

받아줘~ Catch!!
종료

스레드 안에서 발생한 예외를 메인스레드가 제대로 캐치했다.

여기서도 마찬가지로 스레드를 의식하지 않고 동기적인 코드로 예외를 캐치했다.

 

■ Thread와 Task 비교 ~3~ 「다수 스레드의 종료를 기다리고 싶을 때」

케이스 : 정수 1을 반환하는 HeavyMethod1과 정수 2를 반환하는 HeavyMethod2를 병행 실행하여

그 결과를 더해서 콘솔에 3을 출력

・Task의 경우

Program
{
    static void Main(string[] args)
    {
        // 정수 1을 반환하는 HeavyMethod1의 Task를 생성한다.
        Task<int> task1 = Task.Run(() => {
            return HeavyMethod1();
        });
        
        // 정수 2를 반환하는 HeavyMethod2의 Task를 생성한다.
        Task<int> task2 = Task.Run(() => {
            return HeavyMethod2();
        });
        
        // WhenAll의 인수로 생성한 Task를 지정하면 HeavyMethod1, HeavyMethod2가 종료할 때까지
        // 다음 코드를 실행하지 않는다. 즉, 대기한다.
        Task.WhenAll(task1, task2);
        
        Console.WriteLine(task1.Result + task2.Result);
        
        Console.ReadLine();
    }
    
    static int HeavyMethod1()
    {
        Console.WriteLine("매우 무거운 처리1 시작");
        Thread.Sleep(5000);
        Console.WriteLine("매우 무거운 처리1 시작");
        
        return 1;
    }
    
    static int HeavyMethod2()
    {
        Console.WriteLine("매우 무거운 처리2 시작");
        Thread.Sleep(3000);
        Console.WriteLine("매우 무거운 처리2 시작");
        
        return 2;
    }
}

결과

매우 무거운 처리2 시작
매우 무거운 처리1 시작
매우 무거운 처리2 종료
매우 무거운 처리1 종료
3

■ 정리

Task를 사용하면 Thread로 하던 처리를 매우 심플하게 작성할 수 있다.

 

출처 : https://tech-lab.sios.jp/archives/15637 

 

多分わかりやすいC#の非同期処理その1 〜 ThreadとTask 〜 | SIOS Tech. Lab

こんにちは、サイオステクノロジー技術部 武井(Twitter:@noriyukitakei)です。今回は、C#を学び始めた人たちが一番始めに当たる関門であろう非同期処理ついて解説したいと思います。2回シリー

tech-lab.sios.jp

 

반응형

'■ 프로그래밍 언어 > C#' 카테고리의 다른 글

WPF  (0) 2021.03.17
pdb 파일  (0) 2021.03.15
LINQ  (0) 2021.02.14
DLL이란?  (0) 2020.12.09

+ Recent posts