2010-02-12 2 views
7

반복기를 사용할 때마다 중단 될 수있는 예제가 있지만 for 루프와 함께 잘 작동합니다. 모든 코드는 실행중인 메소드의 로컬 변수를 사용합니다. 나는 곤두박질 친다. 내가 알지 못하는 반복자에 대한 사실이 있거나 닷넷에 정직한 버그가 있습니다. 나는 전 (前者)에 걸고있다. 플레 도움.이 코드에서 반복자 (.Net)가 신뢰할 수없는 이유

이 코드는 항상 안정적으로 작동합니다. 그것은 한 번에 하나씩 모든 요소를 ​​하나씩 반복하고 새로운 스레드를 시작하여 메서드의 인수로 새 스레드에 정수를 전달합니다. 각 항목마다 하나씩 10 개의 스레드가 시작됩니다. 1,2,3,4,5,6,7,8,9,10 - 항상 작동합니다.

일하고 CODE :

//lstDMSID is a populated List<int> with 10 elements. 
for(int i=0; i<lstDMSID.Count; i++) 
{ 
    int dmsId = lstDMSID[i]; 
    ThreadStart ts = delegate 
    { 
     // Perform some isolated work with the integer 
     DoThreadWork(dmsId); 
    }; 
    Thread thr = new Thread(ts); 
    thr.Name = dmsId.ToString(); 
    thr.Start(); 
} 

그리고 실제로 요소를 반복 코드. 모든 요소를 ​​한 번에 하나씩 반복하고 새 스레드를 시작합니다. 그것은 10 개의 스레드를 시작하지만 10 개의 정수를 모두 안정적으로 얻지는 못합니다. 나는 그것이 1,2,3,3,6,7,7,8,9,10을 시작하는 것을보고있다. 나는 숫자를 잃어 가고있다.

파열의 CODE : 첫 번째 경우

//lstDMSID is a populated List<int> with 10 elements. 
foreach(int dmsId in lstDMSID) 
{ 
    ThreadStart ts = delegate 
    { 
     // Perform some isolated work with the integer 
     DoThreadWork(dmsId); 
    }; 
    Thread thr = new Thread(ts); 
    thr.Name = dmsId.ToString(); 
    thr.Start(); 
} 
+0

을 반복 정수는 항상 아니다 같은. 그건 무작위로 보인다. –

답변

12

문제는 당신의 범위에 대해 생성 된 폐쇄에 근거은 ..., 당신과 같이 그것을 다시했다 (BAD CODE!) :

// ...Closure now happens at this scope... 
for(int i=0;i<lstDMSID.Count;i++) 
{ 
    ThreadStart ts = delegate 
    { 
     DoThreadWork(lstDMSID[i]); // Eliminate the temporary, and it breaks! 
    }; 
    Thread thr = new Thread(ts); 
    thr.Name = dmsId.ToString(); 
    thr.Start(); 
} 

위임자 (해당 경우 dmsId)의 변수를 닫을 때 문제는 변수가 선언 된 범위에서 종료된다는 것입니다. for 또는 foreach 루프를 사용하면 for/foreach 문 범위에서 클로저가 발생합니다. 이는 너무 높은 수준입니다. 이 문제가 해결됩니다 임시 변수 내부 foreach 루프를 소개

: 무슨 일이 일어나고 있는지에 대한 자세한 설명은

foreach(int dmsId in lstDMSID) 
{ 
    int temp = dmsId; // Add temporary 
    ThreadStart ts = delegate 
    { 
     DoThreadWork(temp); // close over temporary, and it's fixed 
    }; 
    Thread thr = new Thread(ts); 
    thr.Name = dmsId.ToString(); 
    thr.Start(); 
} 

을, 나는 Eric Lippert's blog post: "Closing over the loop variable considered harmful"를 읽고 권하고 싶습니다.

+0

@ 리피드 코 세이 - greate 설명 및 훌륭한 참조 기사. 대답의 수를 기반으로, 나는 이것을 알고 있었을 것입니다. 나는 그것을 지금 알고있다! –

+0

@Jride : 많은 개발자들이 스레딩을 구현할 때까지는 문제가되지 않으므로 많은 개발자들이 놓친다. 필자는 필자의 평행선 협상에서 이것을 정확히 언급한다. –

1

가 dmsId 루프 용의 범위 내에서 선언 된 각 대표는 가변 자체 "예"를 캡처한다.

두 번째 버전에서는 dmsId가 foreach 루프의 전체 범위에 대해 선언됩니다. 각 대리자는 동일한 변수를 캡처합니다. 즉, 잠금없이 여러 스레드에서 동일한 변수에 액세스하고 있다는 것을 의미합니다. 잘못된 요소가 발생할 수 있습니다.

3

이것은 클로저에서 사용중인 변수의 범위 때문입니다.

에릭 리 퍼트 (Eric Lippert)는 nice blog post explaining this을 자세히 기록했으며 다른 사람들 (Jon Skeet?)도 블로그를 작성했다고 생각합니다. 같은 문제는 당신을위한 루프에서 일어날

3

foreach에서 익명 메소드를 수행 할 때 컴파일러는 기본적으로 dmsId를 가리키는 클래스를 생성합니다. 따라서 스레드가 시작될 때마다 각 변수는 동일한 변수를 가리키고 있으므로 스레드가 스케줄 된 시점에 따라 숫자가 중복되거나 건너 뛴다는 것을 알 수 있습니다.

for 루프에서 각 스레드가 고유 한 값을 갖도록 정수의 복사본을 만듭니다.

이 문제에 대한 좋은 데이터가 있습니다 here.

2

문제는 값이 아닌 변수를 클로저하는 것입니다. 이 모든 참가자가 같은 변수에 대한 참조를 받고 있으며, 변수의 값이 루프를 통해 때마다 변경을 의미

이이 문제를 해결해야합니다 참고로

//lstDMSID is a populated List with 10 elements. 
foreach(int dmsId in lstDMSID) 
{ 
    int tempId = dmsId; 
    ThreadStart ts = delegate 
    { 
     //this is method that goes off ad does some isolated work with the integer 
     DoThreadWork(tempId); 
    }; 
    Thread thr = new Thread(ts); 
    thr.Name = tempId.ToString(); 
    thr.Start(); 
} 
+0

그것은 여전히 ​​깨질 것입니다 - 기본적으로 문제와 동일합니다. 도입 된 변수는 foreach 루프 내에서 범위가 지정되어야합니다. 그런 다음 클로저가 제대로 생성됩니다. –

+0

더 나은;) 그게 고정. –

+0

아니요, 답을 작성하기 전에 실수로 제출을 누르십시오. 이제 표시된대로 작동합니다. – Gabe

관련 문제