반응형

1. Task와 async/await

  • async/await: C#의 async와 await 키워드는 비동기 메서드를 쉽게 작성할 수 있게 해준다. async 키워드를 메서드에 붙이면 컴파일러는 해당 메서드를 상태 머신(state machine)코드로 변환한다. await는 비동기 작업이 완료될 때까지 메서드의 실행을 일시 중단(suspend)하고, 작업이 완료되면 메서드를 재개(resume)한다

2. IAsyncStateMachine의 역할

  • 상태 머신(State Machine): async 키워드가 붙은 메서드는 내부적으로 컴파일러에 의해 상태 머신 클래스 코드로 변환된다. 이 상태 머신 클래스는 IAsyncStateMachine 인터페이스를 구현한다. 상태 머신는 비동기 작업의 흐름을 관리하며, await 키워드에 의해 메서드가 일시 중단되고 다시 실행되는 과정을 처리한다
  • MoveNext 메서드: 상태 머신의 핵심 메서드는 MoveNext이다. 이 메서드는 메서드의 실행 상태를 진행시키는 역할을 하며, 비동기 작업이 완료될 때마다 호출된다. await 키워드에 의해 비동기 작업이 일시 중단된 이후, 해당 작업이 완료되면 MoveNext가 호출되어 다음 상태로 넘어가고, 메서드가 재개된다.

아래의 코드에서 ExampleAsync는 상태 머신 클래스로 변환된다.

using System;
using System.Threading.Tasks;

public class Program
{
    public static async Task<int> ExampleAsync()
    {
        // 1초 동안 비동기 대기
        await Task.Delay(1000);
        
        Console.WriteLine("Tset");

        return 42; // 결과 반환
    }

    private static void Main()
    {
        Console.WriteLine("Main : block 1");

        // 비동기 메서드 호출, Task<int> 반환
        var task = ExampleAsync();

        Console.WriteLine("Main : block 2");

        // 결과가 반환될 때까지 대기 (동기적 대기)
        task.Wait();

        Console.WriteLine($"Result: {task.Result}");

        // 프로그램이 종료되지 않도록 대기
        Console.ReadLine();
    }
}

 

디컴파일된 코드는 일반적으로 다음과 같은 형태로 보인다. 아래는 ILSpy를 통해 디컴파일된 코드의 예시

// TaskStateMachine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// Program.<ExampleAsync>d__0
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

[CompilerGenerated]
private sealed class <ExampleAsync>d__0 : IAsyncStateMachine
{
	public int <>1__state;

	public AsyncTaskMethodBuilder<int> <>t__builder;

	private TaskAwaiter <>u__1;

	private void MoveNext()
	{
		int num = <>1__state;
		int result;
		try
		{
			TaskAwaiter awaiter;
			if (num != 0)
			{
				awaiter = Task.Delay(1000).GetAwaiter();
				if (!awaiter.IsCompleted)
				{
					num = (<>1__state = 0);
					<>u__1 = awaiter;
					<ExampleAsync>d__0 stateMachine = this;
					<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
					return;
				}
			}
			else
			{
				awaiter = <>u__1;
				<>u__1 = default(TaskAwaiter);
				num = (<>1__state = -1);
			}
			awaiter.GetResult();
			Console.WriteLine("Tset");
			result = 42;
		}
		catch (Exception exception)
		{
			<>1__state = -2;
			<>t__builder.SetException(exception);
			return;
		}
		<>1__state = -2;
		<>t__builder.SetResult(result);
	}

	void IAsyncStateMachine.MoveNext()
	{
		//ILSpy generated this explicit interface implementation from .override directive in MoveNext
		this.MoveNext();
	}

	[DebuggerHidden]
	private void SetStateMachine(IAsyncStateMachine stateMachine)
	{
	}

	void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
	{
		//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
		this.SetStateMachine(stateMachine);
	}
}

 

 

Task에서 예외가 터질 때 콜 스택에 MoveNext가 찍혀서 처음엔 IEnumerator의 MoveNext인 줄 알았다. 면접 덕분에 조금 더 찾아보게 되고 IAsyncStateMachine의 MoveNext라는 걸 알게 되었다. 이렇게 또 하나 배워간다.

반응형

+ Recent posts