foreach 식별자 및 클로저
다음 두 스 니펫에서 첫 번째는 안전한가요, 아니면 두 번째는해야합니까?
안전하다는 것은 각 스레드가 스레드가 생성 된 동일한 루프 반복에서 Foo의 메서드를 호출하도록 보장된다는 것을 의미합니까?
아니면 새로운 변수 "local"에 대한 참조를 루프의 각 반복에 복사해야합니까?
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Thread thread = new Thread(() => f.DoSomething());
threads.Add(thread);
thread.Start();
}
-
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Foo f2 = f;
Thread thread = new Thread(() => f2.DoSomething());
threads.Add(thread);
thread.Start();
}
업데이트 : Jon Skeet의 답변에서 지적했듯이 이것은 스레딩과 특별히 관련이 없습니다.
편집 :이 모든 변경 사항은 C # 5에서 변수가 정의 된 위치 (컴파일러의 관점에서)로 변경되었습니다. 에서 C # 5 이후, 그들은 동일합니다 .
C # 5 이전
두 번째는 안전합니다. 첫 번째는 그렇지 않습니다.
를 사용 foreach
하면 변수가 루프 외부 에서 선언 됩니다 .
Foo f;
while(iterator.MoveNext())
{
f = iterator.Current;
// do something with f
}
이는 f
클로저 범위가 1 개 뿐이며 스레드가 혼동 될 가능성이 매우 높음을 의미합니다. 일부 인스턴스에서는 메서드를 여러 번 호출하고 다른 인스턴스에서는 전혀 호출하지 않습니다. 루프 내부 의 두 번째 변수 선언으로이 문제를 해결할 수 있습니다 .
foreach(Foo f in ...) {
Foo tmp = f;
// do something with tmp
}
그러면 tmp
각 클로저 범위에 별도 의 항목이 있으므로이 문제의 위험이 없습니다.
다음은 문제에 대한 간단한 증거입니다.
static void Main()
{
int[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach (int i in data)
{
new Thread(() => Console.WriteLine(i)).Start();
}
Console.ReadLine();
}
출력 (무작위) :
1
3
4
4
5
7
7
8
9
9
임시 변수를 추가하면 작동합니다.
foreach (int i in data)
{
int j = i;
new Thread(() => Console.WriteLine(j)).Start();
}
(각 번호는 한 번이지만 주문은 보장되지 않습니다)
Pop Catalin과 Marc Gravell의 대답이 맞습니다. 추가하고 싶은 것은 클로저에 대한 기사 링크입니다 (Java와 C # 모두에 대해 이야기 함). 약간의 가치를 더할 수 있다고 생각했습니다.
편집 : 스레딩의 예측 불가능 성이없는 예제를 제공 할 가치가 있다고 생각합니다. 다음은 두 가지 접근 방식을 보여주는 짧지 만 완전한 프로그램입니다. "bad action"목록은 10 번 10 번 인쇄됩니다. "좋은 조치"목록은 0에서 9까지입니다.
using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
List<Action> badActions = new List<Action>();
List<Action> goodActions = new List<Action>();
for (int i=0; i < 10; i++)
{
int copy = i;
badActions.Add(() => Console.WriteLine(i));
goodActions.Add(() => Console.WriteLine(copy));
}
Console.WriteLine("Bad actions:");
foreach (Action action in badActions)
{
action();
}
Console.WriteLine("Good actions:");
foreach (Action action in goodActions)
{
action();
}
}
}
옵션 2를 사용해야합니다. 변경되는 변수 주변에 클로저를 생성하면 클로저 생성 시가 아니라 변수가 사용될 때 변수 값이 사용됩니다.
Edit: to make it clear, in C# closures are "lexical closures" meaning they don't capture a variable's value but the variable itself. That means that when creating a closure to a changing variable the closure is actually a reference to the variable not a copy of it's value.
Edit2: added links to all blog posts if anyone is interested in reading about compiler internals.
This is an interesting question and it seems like we have seen people answer in all various ways. I was under the impression that the second way would be the only safe way. I whipped a real quick proof:
class Foo
{
private int _id;
public Foo(int id)
{
_id = id;
}
public void DoSomething()
{
Console.WriteLine(string.Format("Thread: {0} Id: {1}", Thread.CurrentThread.ManagedThreadId, this._id));
}
}
class Program
{
static void Main(string[] args)
{
var ListOfFoo = new List<Foo>();
ListOfFoo.Add(new Foo(1));
ListOfFoo.Add(new Foo(2));
ListOfFoo.Add(new Foo(3));
ListOfFoo.Add(new Foo(4));
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Thread thread = new Thread(() => f.DoSomething());
threads.Add(thread);
thread.Start();
}
}
}
if you run this you will see option 1 is definetly not safe.
In your case, you can avoid the problem without using the copying trick by mapping your ListOfFoo
to a sequence of threads:
var threads = ListOfFoo.Select(foo => new Thread(() => foo.DoSomething()));
foreach (var t in threads)
{
t.Start();
}
Both are safe as of C# version 5 (.NET framework 4.5). See this question for details: Has foreach's use of variables been changed in C# 5?
Foo f2 = f;
points to the same reference as
f
So nothing lost and nothing gained ...
참고URL : https://stackoverflow.com/questions/512166/the-foreach-identifier-and-closures
'developer tip' 카테고리의 다른 글
Visual Studio Code로 Angular2 Typescript 디버그 및 실행? (0) | 2020.10.14 |
---|---|
자바 스크립트 효율성 : 'for'대 'forEach' (0) | 2020.10.14 |
order () 함수 이해 (0) | 2020.10.14 |
Visual Studio 2012의 웹 사이트 관리 도구는 어디에 있습니까? (0) | 2020.10.14 |
PHP에서 문자열의 일부를 어떻게 바꾸나요? (0) | 2020.10.14 |