문제

다음 두 개의 스 니펫에서 첫 번째 스 니펫이 안전합니까, 아니면 두 번째 스 니펫을해야합니까?

안전하다는 것은 각 스레드가 스레드가 생성 된 것과 동일한 루프 반복에서 FOO의 메소드를 호출하도록 보장된다는 것을 의미합니까?

아니면 루프의 각 반복에 새로운 변수 "로컬"에 대한 참조를 복사해야합니까?

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
}

이것은 단지 1 만 있음을 의미합니다 f 클로저 범위와 관련하여 스레드는 혼란 스러울 수 있습니다. 일부 인스턴스에서 여러 번 메소드를 호출하지는 않습니다. 두 번째 변수 선언 으로이 문제를 해결할 수 있습니다 내부에 루프 :

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();
        }

(각 숫자는 한 번이지만 물론 주문은 보장되지 않습니다)

다른 팁

팝 카탈린과 마크 그라벨의 대답이 맞습니다. 내가 추가하고 싶은 것은 링크입니다 폐쇄에 관한 내 기사 (Java와 C#에 대해 이야기합니다). 약간의 가치를 더할 수 있다고 생각했습니다.

편집 : 스레딩의 예측 불가능 성이없는 모범을 보일 가치가 있다고 생각합니다. 다음은 두 가지 접근 방식을 모두 보여주는 짧지 만 완전한 프로그램입니다. "나쁜 행동"목록은 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를 사용해야 할 필요가 있으므로 변경 변수를 중심으로 클로저를 생성하면 변수가 사용되고 폐쇄 생성 시간이 아닌 변수의 값이 사용됩니다.

C#에서 익명 방법의 구현과 그 결과 (1 부)

C#에서 익명 방법의 구현과 그 결과 (2 부)

C#에서 익명 방법의 구현과 그 결과 (3 부)

편집 : 명확하게하기 위해 C# 클로저에서는 "어휘 폐쇄"변수의 값을 캡처하지 않고 변수 자체를 캡처한다는 의미입니다. 즉, 변경 변수로의 폐쇄를 만들 때 폐쇄는 실제로 값의 사본이 아닌 변수에 대한 참조입니다.

EDIT2 : 컴파일러 내부에 대해 읽는 데 관심이있는 사람이라면 모든 블로그 게시물에 링크가 추가되었습니다.

이것은 흥미로운 질문이며 사람들이 다양한 방식으로 대답하는 것을 본 것 같습니다. 나는 두 번째 방법이 유일한 안전한 방법이 될 것이라는 인상을 받았습니다. 나는 진짜 빠른 증거를 휘젓었다 :

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();
        }
    }
}

당신이 이것을 실행하면 당신은 옵션 1이 확실히 안전하지 않습니다.

귀하의 경우, 귀하는 매핑하여 복사 트릭을 사용하지 않고 문제를 피할 수 있습니다. ListOfFoo 스레드 시퀀스로 :

var threads = ListOfFoo.Select(foo => new Thread(() => foo.DoSomething()));
foreach (var t in threads)
{
    t.Start();
}

둘 다 C# 버전 5 (.NET Framework 4.5) 기준으로 안전합니다. 자세한 내용은이 질문을 참조하십시오. Foreach의 변수 사용이 C# 5에서 변경 되었습니까?

Foo f2 = f;

동일한 참조를 가리 킵니다

f 

그래서 잃어버린 것은없고 얻지 못했습니다 ...

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top