Synchronizacja wątków oraz zdarzeń - lock?

Odpowiedz Nowy wątek
2011-10-19 04:09
0

Witam.

W jaki sposób C# synchronizuje wątki? Czy w przypadku wywołania metody poprzez delegat tworzony jest osobny wątek? Jeśli tak to w jaki sposób synchronizować zapis/odczyt danych w wątku tworzonym przez obsługę wywołania delegata, oraz wątkiem tworzonym jawnie przez programistę? Próbowałem synchronizować poprzez użycie słowa kluczowego lock, ale w wątku przez mnie utworzonym występuje błąd.

Kod w uproszczeniu wygląda następująco:

  1. Tworzone są 4 wątki ( w uproszczeniu)
  2. Wątek pierwszy subskrybuje zdarzenia wątku 2 i vice versa, 2-3, 3-4.
  3. Poniższy kod wykonuje każdy z wątków, metoda NeighborDiscoverySend oraz NeighborTableSolicitation wywołuje zdarzenia, które są odbierane i wykonywane przez subskrybentów.
        public void process()
        {

            while(this.thread_processing == true)
            {

                    NeighborDiscoverySend();

                    //Tu błąd: "Collection was modified after the enumerator was instantiated."
                    //Rozumiem że iteruje po kolekcji do której został dodany/usunięty element w trakcie iterowania po kolekcji.
                    //Dlaczego skoro w obsłudze zdarzenia zablokowaliśmy dostęp do kolekcji (lock(sync))

                    foreach (NeighborTable n in myNeighborTable)
                    {

                        if (n.NeighborID != this.myID)
                        {

                            NeighborTableSolicitation(n.NeighborTimeStampTicks);

                        }

                    }

                    Thread.Sleep(5000);

                }

            }
        }
  1. Obsługa zdarzenia wygląda następująco:
        private void NeighborDiscoveryReceive(NetworkMessage msg)
        {

            lock (sync)
            {

                if (myNeighborTable.Count == 0)
                {

                    this.howManyNodesIntNetwork++;
                    this.myNeighborTable.AddFirst(new NeighborTable(this.myTimeStampTicks, this.myName, this.myID, "Me"));
                    this.howManyNodesIntNetwork++;
                    this.myNeighborTable.AddLast(new NeighborTable(msg.senderTimeStampTicks, msg.senderName, msg.senderID, "Short"));

                }

                else
                {

                    if (NeighborTable.Exist(this.myNeighborTable, msg.senderName) == false)
                    {

                        this.howManyNodesIntNetwork++;
                        this.myNeighborTable.AddLast(new NeighborTable(msg.senderTimeStampTicks, msg.senderName, msg.senderID, "Short"));

                    }

                }

                if (this.myNeighborTable.Count != 0)
                {

                    {
                        myNeighborTable = NeighborTableSort(this.myNeighborTable);
                        myNeighborTable = UpdateID(this.myNeighborTable);
                        this.UpdateMyID(this.myNeighborTable);
                    }

                }

            }

        }
  1. Dodam, że powyższy błąd występuje z częstotliwością 1/3, czasem działa, czasem nie ;]

  2. Całość kodu: http://www.speedyshare.com/files/30816329/TestSynchro.rar

Pozostało 580 znaków

2011-10-19 04:54
0

Czy w przypadku wywołania metody poprzez delegat tworzony jest osobny wątek?
nie.

na podstawie pokazanego kodu nie da się nic wywnioskować, bo wywołujesz NeighborTableSolicitation, a pokazałeś NeighborDiscoveryReceive.

Pozostało 580 znaków

2011-10-19 12:11
1
Motoorhead napisał(a)
  1. Dodam, że powyższy błąd występuje z częstotliwością 1/3, czasem działa, czasem nie ;]

1/3 Hz?


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

2011-10-19 14:04
Motoorhead
0

Wybacz poprzedni kod, pisałem go późno w nocy, faktycznie jest nieczytelny i ciężko coś wywnioskować. Kod poniżej jest o wiele lepszy i lepiej oddaje sytuację, w której występuje wyjątek "Collection was modified after the enumerator was instantiated."
Anyway, jakieś pomysły? Problem dotyczy modyfikowania kolekcji w trakcie jej iterowania w pętli. Kolekcja jest modyfikowana w metodzie obsługi wywołania delegata. Sęk w tym żeby zmusić jakoś poniższy kod do wykonywania się "atomowo". Tzn. jeżeli przyszło jakieś zdarzenie, to kod nie wędrował od razu do jego obsługi(i modyfikował kolekcję) tylko poczekał aż pętla "foreach (int n in myNeighborTable)" się zakończy i dopiero wykonywał obsługę zdarzenia z delegata.

    class Program
    {

        static void Main(string[] args)
        {

            Node n1 = new Node();
            Node2 n2 = new Node2();

            n2.OnMessageSent += new Node2.SendMessage(n1.onReceive);

            Thread t1 = new Thread(n1.process);
            Thread t2 = new Thread(n2.do_work);

            t1.Start();
            t2.Start();

            Console.ReadLine();

        }
    }    

    public class Node
    {

        private Object sync = new Object();
        private volatile LinkedList<int> myNeighborTable = new LinkedList<int>();

        public void process(object obj)
        {

            while (true)
            {
                //W tej pętli występuje wyjątek
                foreach (int n in myNeighborTable)
                {

                    Console.WriteLine(n.ToString());

                }

                Thread.Sleep(1000);
            }
        }

        public void onReceive(int msg)
        {

            lock (sync)
            {

                if (myNeighborTable.Count == 0)
                {

                    this.myNeighborTable.AddFirst(msg);

                }

                else
                {

                    this.myNeighborTable.AddLast(msg);

                }

            }

        }

    }

    public class Node2
    {

        public delegate void SendMessage(int msg);
        public SendMessage OnMessageSent;
        int i = 0;

        public void do_work()
        {

            while (true)
            {

                this.OnMessageSent(i);
                i++;
                Thread.Sleep(1000);

            }

        }

    }

Pozostało 580 znaków

2011-10-19 14:57
Gues
0
Motoorhead napisał(a)

poczekał aż pętla "foreach (int n in myNeighborTable)" się zakończy i dopiero wykonywał obsługę zdarzenia z delegata.

No to foreacha również umieść w lock(sync)

Pozostało 580 znaków

2011-10-19 15:30
Motoorhead
0
Gues napisał(a)
Motoorhead napisał(a)

poczekał aż pętla "foreach (int n in myNeighborTable)" się zakończy i dopiero wykonywał obsługę zdarzenia z delegata.

No to foreacha również umieść w lock(sync)

Będzie deadlock.

Jak kolega wyżej słusznie zwrócił uwagę, metody wywoływane poprzez delegat nie są wykonywane w osobnym wątku więc podwójny lock spowoduje zakleszczenie. Jeżeli dobrze kombinuję, to lock, w tym przykładzie i tak nie jest potrzebny (bo i tak mamy tylko jeden wątek i zasoby nie są współdzielone). Jednak w dalszym ciągu nie rozwiązana jest kwestia modyfikowania kolekcja w trakcie jej iterowania.

Pozostało 580 znaków

2011-10-19 15:48
Gues
0

Tak, delegaty nie są wywoływane w osobnych wątkach, więc zostaną wykonane w wątku t2. A foreach jest w wątku t1. Tak więc lock jest potrzebny.
Co więcej wątków wysyłających zdarzenie ma być kilka (o ile dobrze zrozumiałem poprzedni post), więc jako że LinkedList nie jest thread safe, to lock tak czy siak przy dodawaniu do listy powinien być.

Dodatkowo nie widzę powodu, dla którego miałoby wystąpić tutaj zakleszczenie

Pozostało 580 znaków

2011-10-19 22:14
0

Czy jeżeli nie wolno modyfikować kolekcji w pętli foreach, to wolno ją modyfikować z innego wątku?


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0