Identyfikator foreach i zamknięcia
W dwóch kolejnych fragmentach, czy pierwszy jest Bezpieczny, czy musisz zrobić drugi?
Przez safe mam na myśli czy każdy wątek ma zagwarantowane wywołanie metody na Foo z tej samej iteracji pętli, w której wątek został utworzony?
Czy musisz skopiować odniesienie do nowej zmiennej "local"do każdej iteracji pętli?
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();
}
Update: jak wskazano w odpowiedzi Jona Skeeta, nie ma to nic wspólnego z gwintowanie.
7 answers
Edit: to wszystko zmienia się w C# 5, ze zmianą miejsca zdefiniowania zmiennej (w oczach kompilatora). Od C # 5, są takie same .
Przed C#5
Drugi jest bezpieczny, a pierwszy nie.
Z foreach
zmienna jest deklarowana poza pętlą-tzn.
Foo f;
while(iterator.MoveNext())
{
f = iterator.Current;
// do something with f
}
Oznacza to, że jest tylko 1 f
pod względem zakresu zamknięcia, a wątki mogą się bardzo pomylić-wywołanie metody wiele razy w niektórych przypadkach, a nie w ogóle w innych. Można to naprawić za pomocą drugiej deklaracji zmiennej wewnątrz pętli:
foreach(Foo f in ...) {
Foo tmp = f;
// do something with tmp
}
To ma wtedy oddzielny tmp
w każdym zakresie zamknięcia, więc nie ma ryzyka tego problemu.
Oto prosty dowód problemu:
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();
}
Wyjścia (losowo):
1
3
4
4
5
7
7
8
9
9
Dodaj zmienną temp i działa:
foreach (int i in data)
{
int j = i;
new Thread(() => Console.WriteLine(j)).Start();
}
(każda liczba raz, ale oczywiście kolejność nie jest gwarantowana)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-13 20:23:13
Odpowiedzi Pop Catalina i Marca Gravella są poprawne. Chcę tylko dodać link do mojego artykułu o closures (który mówi zarówno o Javie jak i C#). Pomyślałem, że to może dodać trochę wartości.
EDIT: myślę, że warto podać przykład, który nie ma nieprzewidywalności wątków. Oto krótki, ale kompletny program pokazujący oba podejścia. Lista "złych działań" wyświetla się 10 razy; lista "dobrych działań" liczy się od 0 do 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();
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-04 17:32:23
Musisz użyć opcji 2, tworząc zamknięcie wokół zmieniającej się zmiennej użyje wartości zmiennej, gdy zmienna jest używana, a nie w czasie tworzenia zamknięcia.
Implementacja metod anonimowych w C# i jej konsekwencje (Część 1)
Implementacja metod anonimowych w C# i jej konsekwencje (część 2)
Implementacja metod anonimowych w C# i jej konsekwencje (część 3)
Edit: żeby było jasne, w C# closures są " leksykalne closures ", co oznacza, że nie przechwytują wartości zmiennej, ale samą zmienną. Oznacza to, że podczas tworzenia zamknięcia do zmieniającej się zmiennej zamknięcie jest w rzeczywistości odniesieniem do zmiennej, a nie kopią jej wartości.
Edit2: dodano linki do wszystkich postów na blogu, jeśli ktoś jest zainteresowany poczytaniem o wewnętrznych kompilatorach.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-04 17:27:24
To ciekawe pytanie i wydaje się, że widzieliśmy, jak ludzie odpowiadają na różne sposoby. Miałem wrażenie, że druga droga będzie jedyną bezpieczną drogą. Mam bardzo szybki dowód:
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();
}
}
}
Jeśli to uruchomisz, zobaczysz, że opcja 1 jest zdecydowanie niebezpieczna.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-04 17:20:34
W Twoim przypadku możesz uniknąć problemu bez użycia sztuczki kopiowania, mapując ListOfFoo
do sekwencji wątków:
var threads = ListOfFoo.Select(foo => new Thread(() => foo.DoSomething()));
foreach (var t in threads)
{
t.Start();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-20 12:55:35
Oba są bezpieczne od wersji C# 5 (. NET framework 4.5). Zobacz to pytanie po szczegóły: Czy użycie zmiennych foreacha zostało zmienione w C# 5?
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:14:57
Foo f2 = f;
Wskazuje na to samo odniesienie co
f
Więc nic straconego i nic zyskanego ...Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-04 16:47:15