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

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

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.

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?

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

            }

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

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.

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

0

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

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