Wątki - zasada działania i synchronizacja

0

Witam wszystkich

Mam ciężki problem, bo próbuję zrozumieć zasadę działania wątków i średnio mi to idzie.

Potrafię już tworzyć proste programy, gdzie nie trzeba synchronizować zmiennych, ale załóżmy taki problem:
jest jedna lista, i trzy wątki. Pierwsze dwa dodają do tej listy po jednym elemencie (może być string) co powiedzmy 500 ms, a trzeci sprawdza czy tablica jest pusta i jeżeli nie, to usuwa pierwszy element co 800 ms tak, że liczba elementów w liście ciągle rośnie. Jak przekroczy jakąś liczbę, to zamyka wszystkie wątki.

Jestem w rozsypce - kompilator maże mi po ekranie wyjątkami, jakimiś "cross-threading ble ble ble", niebezpiecznymi metodami itd., a msdn opisuje tylko proste przykłady ze zwykłym progress barem lub txtBoxem.
Będę wdzięczny za każdą pomoc.

0

po pierwsze nalezy synchronizowac dostep do zmiennych, ktorych wartosci sie zmienia
w c# tzw. sekcje krytyczna najlatwiej uzyskac przez blok lock

lock(jakis_obiekt)
{
jestem jakims watkiem i cos tu robie, jesli ktos inny bedzie chcial zrobic lock na tym samym obiekcie bedzie czekal az ja skoncze
}

wiecej o lock: http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx

jesli uzywasz generic list (List<T>) to masz wlasciwosc ICollection.SyncRoot (musisz zrzutowac na ICollection) i tej wlasciwosci powinienes uzywac do synchronizacji dostepu zarowno do dodawania, jak i do usuwania z listy

jesli chcesz miec walsny obiekt na ktorym robisz lock, zazwyczaj deklaruje sie go:
public/protected/private [static] readonly object locker = new object();
poziom dostepu juz zalezy od ciebie
static jesli w calym appl domain potrzebujesz taki obiekt
ale mozesz tez na dowolnym innych obiekcie robic lock
np.
MyClass c1 = new MyClass();

lock (c1)
{
// sekcja krytyczna
}

0

Tu masz ebooka o programowaniu wielowątkowym w c#:http://www.albahari.com/threading/. Jak cos nie bedzie jasne to pytaj.

0

Myślałem, że z pracy odpiszę, ale nie dałem rady.

Zaraz zabiorę się za czytanie Waszych porad ale już chyba widzę, gdzie popełniłem błąd :).

0

Jestem wdzięczny za podpowiedzi, ale niestety zaszły pewne komplikacje.

ICollection coll = lista;
lock (coll.SyncRoot)
{
                    //tylko foreach lub count :/
}

Po zrzutowaniu listy na ICollection nie mogę dodawać ani usuwać z niej elementów, zresztą tutaj http://msdn.microsoft.com/en-us/library/system.collections.icollection.syncroot(VS.71).aspx nie jest zbyt dużo napisane.

Udało mi się zrealizować postawione wcześniej zadanie, ale pewnie w strasznie toporny sposób. Jeżeli ktoś ma jakieś sugestie to są bardzo mile widziane.

Dzięki wszystkim za pomoc - była bardzo przydatna!

Oto mój kod:

public partial class Form1 : Form
    {
        Thread t1, t2, t3;
        public Form1()
        {
            InitializeComponent();
        }
        private void btnStart_Click(object sender, EventArgs e)
        {
            t1 = new Thread(new ThreadStart(addToList1));
            t1.Start();
            t2 = new Thread(new ThreadStart(addToList2));
            t2.Start();
            t3 = new Thread(new ThreadStart(removeFromList));
            t3.Start();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }
        List<string> lista = new List<string>();
        static readonly object locker = new object();
        
        private void addToList1()
        {
            while (true)
            {
                string watek1 = "watek 1";
                ICollection coll = lista;
                lock (coll.SyncRoot)
                {
                    lista.Add(watek1);
                    odświerzListę();
                }
                SetText(watek1 + Environment.NewLine);
                Thread.Sleep(2000);
            }
        }
        private void addToList2()
        {
            while (true)
            {
                string watek2 = "watek2";
                ICollection coll = lista;
                lock (coll.SyncRoot)
                {
                    lista.Add(watek2);
                    odświerzListę();
                }
                SetText(watek2 + Environment.NewLine);
                Thread.Sleep(1000);
            }
        }
        private void removeFromList()
        {
            while (true)
            {
                ICollection coll = lista;
                lock (coll.SyncRoot)
                {
                    if (lista.Count > 0)
                    {
                        lista.RemoveAt(0);
                    }
                    odświerzListę();
                }
                Thread.Sleep(1000);
            }
        }
        private void odświerzListę()
        {
            ClearItem();
            foreach (string s in lista)
            {
                AddItem(s);
            }
        }
        delegate void SetTextCallback(string text);
        private void SetText(string text)
        {
            if (this.txtBox.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.txtBox.Text += text;
            }
        }
        delegate void AddItemCallback(string item);
        private void AddItem(string item)
        {
            if (this.lbLista.InvokeRequired)
            {
                AddItemCallback d = new AddItemCallback(AddItem);
                this.Invoke(d, new object[] {item});
            }
            else
            {
                this.lbLista.Items.Add(item);
            }
        }

        delegate void ClearItemsCallback();
        private void ClearItem()
        {
            if (!lbLista.InvokeRequired)
                this.lbLista.Items.Clear();
            else
            {
                ClearItemsCallback d = new ClearItemsCallback(ClearItem);
                this.Invoke(d);
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            t1.Abort();
            t2.Abort();
            t3.Abort();
        }
    }
0

zadania realizowane w watkach i lock sa ok
nie bardzo tylko rozumiem metode odświerzListę
nie ma potrzeby czyszczenia za kazdym razem listboxa i dodawania do niego element po elemencie
wystarczy ze w form_load przypiszesz ta liste jako data source listboxa
lbLista.DataSource = lista;
natomiast po zmodyfikowaniu listy w watku wystarczy ze wywolasz Invalidate() na listbox, oczywiscie z uwzglednieniem InvokeRequired
chyba jak uzyjesz BindingList<T> to uaktualnienie powinno nastapic samo
jesli z jakiegos powodu musisz recznie dodawac itemy do listbox mozesz uzyc ObservableCollection<T> i w gui podpiac sie pod odpowiednie eventy, a jesli nie musisz recznie, to tez wystarczy uzyc tej kolekcji jako datasource, ona implementuje INotifyCollectionChanged, wiec update powienien nastepowac automatycznie, bez koniecznosci wolania Invoke

0

Tyle cennych uwag... i już się zabieram do przerabiania programu.
Zobaczymy co z tego wyniknie.

0

massther zrobiłem tak jak napisałeś z odświerzaniem listBoxa, ale niestety nie było tak kolorowo jak myślałem. Niestety invalidate nie zadziałał, a dświerzanie zadziałało dopiero w takiej wersji:

private void Form1_Load(object sender, EventArgs e)
        {
            lbLista.DataSource = lista;
            lbLista.DisplayMember = "JestemNapisem";
        }
//Reszta kodu...
delegate void InvalidateListCallback();
        private void InvalidateList()
        {
            if (this.lbLista.InvokeRequired)
            {
                InvalidateListCallback d = new InvalidateListCallback(InvalidateList);
                this.Invoke(d);
            }
            else
            {
                ((CurrencyManager)lbLista.BindingContext[lbLista.DataSource]).Refresh();
            }
        }
//Reszta kodu...
class Napis
        {
            public Napis(string napis)
            {
                NapisWew = napis;
            }
            string NapisWew;
            public string JestemNapisem { get{ return NapisWew;}
            set{NapisWew = value;} }
        }
0

Pozwólcie, że się podepnę do tematu. Chciałem zapytać co myślicie o rozwiązaniu tego typu (kod w VB.NET ale chyba wszyscy będą wiedzieć o co chodzi):

    Friend WithEvents Watki As WatkiClass = New WatkiClass()
    Friend Delegate Sub RefreshUI_Callback(ByVal Wytyczna As String, ByVal Dane As Object)
    Private Sub RefreshUI(ByVal Wytyczna As String, ByVal Dane As Object) Handles Watki.RefreshUI
        Try
             If Me.InvokeRequired AndAlso Not Me.Disposing AndAlso Not Me.IsDisposed Then
                 Dim d As New RefreshUI_Callback(RefreshUI_Do)
                 Me.Invoke(d, New Object() {Wytyczna, Dane})
             ElseIf Not Me.Disposing AndAlso Not Me.IsDisposed Then
                 RefreshUI_Do(Wytyczna, Dane)
             End If
        Catch ex As Exception
        End Try
    End Sub

    Private Sub RefreshUI_Do(ByVal Wytyczna As String, ByVal Dane As Object)
        Select Case Wytyczna
            Case Is = "AddItem"
                 'Jakiś kod...
            Case Is = "ClearItem"
                 'Jakiś kod...
            Case Else
        End Select
    End Sub

Jest klasa np. WatkiClass z której uruchamiane są nowe wątki, zawiera deklarację:

Friend Event RefreshUI(ByVal Wytyczna As String, ByVal Dane As Object)

Potem do aktualizacji interfejsu z poziomu dowolnego wątku tej klasy wystarczy wywołać:

RaiseEvent RefreshUI("AddItem", "cokolwiek")

Chodzi o to, że w sumie nie trzeba pisać nowych kawałków kodu dla każdego nowego zdarzenia na formularzu. Teoretycznie jeśli Me.InvokeRequired = True to dla każdej kontrolki potomnej zawartej na tym formularzu też Invoke będzie wymagane. Prosiłbym o opinię na temat tego rozwiązania i wskazanie ewentualnych przeciwwskazań do jego stosowania. Jak na razie w moich projektach się sprawdza. ;)

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