달짱달짱

[C#] 인덱스 벗어남 오류 : 클로져 (Closure) 본문

C#

[C#] 인덱스 벗어남 오류 : 클로져 (Closure)

달콩쨩 2023. 7. 24. 15:21

for 문을 이용하여 아래와 같은 코드를 실행 시켰을 때, "System.IndexOutOfRangeException: '인덱스가 배열 범위를 벗어났습니다" 오류가 발생하였다. 해당 오류는 배열의 인덱스 범위를 벗어났을 때 발생하는 오류였다. 

for(int i=0; i<arr.Length; i++)
{
	Task.Run(() => MainTask(arr[i]));
}

하지만, 다른 언어에서는 위와 같이 for 문 안에서 다른 함수를 호출하였을 때 해당 오류가 나지않는다. 확인해보니 arr.length 는 2여서 i =0, i=1 일때만 타야하는데 arr[2] 일때도 MainTask 를 호출하도록 하고 있었다. 따라서 할당되지 않은 arr[2] 를 인자로 넘겨주려니 인덱스 범위를 벗어났다는 오류가 발생한 것이다. 

 

관련하여 검색해보니 이는 c# 의 클로저로 인한 오류였다. 클로저는 외부 변수나 필드와 같은 '환경'을 저장하고 있는 함수를 뜻한다. C# 에서는 람다식을 사용하여 해당 내용을 전달 할 때, 외부의 변수나 필드를 사용하는 경우 Closure 처리가 된다. 

 

Task.Run(() => MainTask(arr[i])); 코드는 람다식을 사용하여 MainTask를 비동기로 호출하는 부분이다. 하지만 클로저로 인해 문제가 발생한다.

 

반복문의 for 루프에서 i는 계속 증가하며 MainTask를 호출할 때 i의 값도 변하게 된다. 따라서 비동기 호출인 Task.Run에 의해 MainTask가 실행될 때, 이미 for 루프가 종료된 후의 i 값이 사용되게 된다.

예를 들어, arr.Length가 2일 경우 i는 0과 1까지 증가한다. 그리고 MainTask를 호출할 때 람다식이 Task.Run(() => MainTask(arr[i])) 호출 이후에 실행될 수 있으며, 이때 i의 최종 값인 2가 사용된다. 그러나 arr 배열의 인덱스는 0부터 시작하므로 deviceInfos[2]는 실제로는 없는 인덱스이다. 따라서 오류가 발생할 수 있다.

이러한 문제를 해결하기 위해서는 클로저의 동작을 피하는 방법을 사용해야 한다. 예를 들어, i의 값을 람다식의 인자로 넘겨주어 아래와 같은 방법으로 해결하였다. 

for(int i=0; i<arr.Length; i++)
{
	int index = i;
	Task.Run(() => MainTask(arr[index]));
}