Autor Beitrag
funcry
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 110
Erhaltene Danke: 1

Win7 64, XP 32
C# (VS 2010 EE), Delphi (TD 2006 Win32)
BeitragVerfasst: So 26.04.09 12:15 
An diesem Problem beiße ich mir derzeit die Zähne aus.

Problem:
Nach der Abarbeitung einer Reihe von Threads möchte ich einen Event:
ausblenden C#-Quelltext
1:
csv_files_added(thisnew CSV_Control_EventArgs(current_csv_core));					

auslößen - jedoch, das Event soll nicht im Thread-Kontext aufgerufen werden, weil das nachfolgend Probleme verursacht. Innerhalb jedes einzelnen Threads werden Events ausgelößt welche für das UI nötig sind, weshalb der GUI Thread nicht blockiert werden darf. Im Prinzip würde ein BackgroudWorker passen, jedoch ist die Klasse ohne GUI.

Hier der ursprüngliche Ansatz:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
public void set_csv_files(string path)
        {
            this.all = new List<CSV_Core>();

            DirectoryInfo di = new DirectoryInfo(path);
            FileInfo[] fi = di.GetFiles("*.*");

            int cnt_threads = fi.GetLength(0);
            for (int i = 0; i < fi.GetLength(0); i++)
            {
                ThreadPool.QueueUserWorkItem(delegate(object param)
                {
                    FileInfo _fi = (FileInfo)param;
                    CSV_Core current_csv_core = new CSV_Core(_fi.FullName, Config.csv_field_seperator, Config.csv_decimal_thousand_seperator, Config.csv_cnt_columns, Config.csv_symbol_column, Config.csv_date_column, Config.csv_data_column);

                    this.all.Add(current_csv_core);

                    if (csv_file_added != null) csv_file_added(thisnew CSV_Control_EventArgs(current_csv_core));

                    if (Interlocked.Decrement(ref cnt_threads) == 0)
                        if (csv_files_added != null) csv_files_added(thisnew CSV_Control_EventArgs(current_csv_core));
                }, fi[i]);
            }
        }


Meine erste Überlegung war, anstelle des ThreadPools einach Threads verwenden, und diese mit join abschließen. Das Problem dabei, ich blockiere den GUI Thread was sofort zu einem deathlock führt.

Lösungsansatz:
Ich nutze anstelle des ThreadPools BeginInvoke. Hier habe ich einen Callback welcher vielleicht nicht im Thread-Kontext aufgerufen wird. Möglicherweise habe ich den Mechanismus nicht korrekt angewendet. Zwar arbeitet das Programm, aber auch hier ist das letzte Event im Thread-Kontext.

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
private delegate void _del_scf(FileInfo _fi);
        private int _cnt_scf;

        public void set_csv_files(string path)
        {
            this.list_csv_core = new List<CSV_Core>();

            DirectoryInfo di = new DirectoryInfo(path);
            FileInfo[] fi = di.GetFiles("*.*");

            this._cnt_scf = fi.GetLength(0);
            for (int i = 0; i < fi.GetLength(0); i++)
            {
                _del_scf d = new _del_scf(_dowork_scf);
                d.BeginInvoke(fi[i], new AsyncCallback(_callback_scf), d);
            }
        }

        private void _dowork_scf(FileInfo _fi)
        {
            CSV_Core current_csv_core = new CSV_Core(_fi.FullName, Config.csv_field_seperator, Config.csv_decimal_thousand_seperator, Config.csv_cnt_columns, Config.csv_symbol_column, Config.csv_date_column, Config.csv_data_column);

            this.list_csv_core.Add(current_csv_core);
            if (csv_file_added != null) csv_file_added(thisnew CSV_Control_EventArgs(current_csv_core));

            Interlocked.Decrement(ref this._cnt_scf);
        }

        private void _callback_scf(IAsyncResult ar)
        {
            _del_scf d = (_del_scf)ar.AsyncState;
            d.EndInvoke(ar);

            if (this._cnt_scf == 0)
            {
                if (csv_files_added != null) csv_files_added(thisnew CSV_Control_EventArgs(null));
            }
        }


Unabhängig davon dass ich mein Ziel damit nicht erreiche - auch hier wird:
ausblenden C#-Quelltext
1:
csv_files_added(thisnew CSV_Control_EventArgs(null));					

im Thread kontext aufgerunfen, stört mich an dieser Lösung vorallem, dass ich eine globale Variable this._cnt_scf brauche um herauszufinden wann der letzte Thread beendet wurde.

Da ich nur vc express nutze fehlt mir leider eine genaue Darstellung der Threads. Um herauszufinden ob das letzte Event im thread-Kontext aufgerunfen wird nutze in in meiner GUI-Klasse dieses Statement:
ausblenden C#-Quelltext
1:
if (dgv_csv_master.InvokeRequired) dgv_csv_master.Invoke(new MethodInvoker(delegate { _csv_control_csv_files_added(sender, e); }));					
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: So 26.04.09 12:59 
user profile iconfuncry hat folgendes geschrieben Zum zitierten Posting springen:
jedoch, das Event soll nicht im Thread-Kontext aufgerufen werden, weil das nachfolgend Probleme verursacht.
user profile iconfuncry hat folgendes geschrieben Zum zitierten Posting springen:
Im Prinzip würde ein BackgroudWorker passen, jedoch ist die Klasse ohne GUI.
Das ist für mich ein Widerspruch: Wenn die Klasse GUI-unabhängig ist, kann sie sich nicht um die Synchronisation kümmern, das muss ihr Konsument erledigen. Dummerweise ;) gibt es trotzdem dafür eine Lösung: Feuere die Events innerhalb von SynchronizationContext.Current.Post ab.
user profile iconfuncry hat folgendes geschrieben Zum zitierten Posting springen:
Hier habe ich einen Callback welcher vielleicht nicht im Thread-Kontext aufgerufen wird.
Nope, es findet kein Threadwechsel statt. Was ja eben, von SynchronizationContext abgesehen, überhaupt nicht möglich wäre.
user profile iconfuncry hat folgendes geschrieben Zum zitierten Posting springen:
[...] stört mich an dieser Lösung vorallem, dass ich eine globale Variable this._cnt_scf brauche um herauszufinden wann der letzte Thread beendet wurde.
Könntest du vermeiden, indem du den Callback als anonyme Methode/Lambda einbettest.
user profile iconfuncry hat folgendes geschrieben Zum zitierten Posting springen:
Da ich nur vc express nutze fehlt mir leider eine genaue Darstellung der Threads.
Wie meinen :gruebel: ?

PS: Da du ja schon mit der TPL gearbeitet hast: Mit der sollte sich die Interlocked-Klasse vermeiden lassen. Ansonsten: Lass wenigstens diese fürchterlichen Unterstriche weg :P .


Edit: Dein Code ist nicht threadsafe, du müsstest um jeden Zugriff auf all locken.

_________________
>λ=
funcry Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 110
Erhaltene Danke: 1

Win7 64, XP 32
C# (VS 2010 EE), Delphi (TD 2006 Win32)
BeitragVerfasst: So 26.04.09 14:08 
Danke für die Antworten!
An diesem Problem habe ich das ganze WE getüftelt :-)

Die Unterstriche werde ich später vermutlich noch rausnehmen, ist mit dem Refractor ja nicht allzu aufwändig.
funcry Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 110
Erhaltene Danke: 1

Win7 64, XP 32
C# (VS 2010 EE), Delphi (TD 2006 Win32)
BeitragVerfasst: Di 28.04.09 15:02 
Nach einigem Abwägen habe ich deinen Ratschlag versucht umzusetzen. Ich habe deshalb gezögert, weil ich es auch als elegant empfunden hätte, wenn die GUI sich, mit eher komplizierten Dingen wie Threading, nicht beschäftigten müsste, sonder einfach eine Klasse nutzen könnte, welche dies erledigt. Andererseits geht mir auch darum einen sauberen Programmier-Stil zu verwenden.
Eigentlich wollte ich dabei auch gleich die Unterstriche herausnehmen, was mir jedoch nicht gelingen will. Da ich ja auch auf die Großschreibung bei Variablen verzichte, bekomme ich einfach keine lesbaren Variablen bzw. Methoden hin. Über Ideen und Hinweise - auch ganz allgemein zur Programmierung des folgenden Codes, freue ich mich.

Hier die aktuelle Umsetzung:

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
        private void _UI_DGV_CSV_Load(object sender, EventArgs e)
        {

            bgw_set_files.RunWorkerAsync(Config.path_csv);
        }

private void bgw_set_files_DoWork(object sender, DoWorkEventArgs e)
        {
            string path = e.Argument as string;
            this.list_all_csv_cores = new List<CSV_Core>();
            ManualResetEvent mre_done = new ManualResetEvent(false);

            DirectoryInfo di = new DirectoryInfo(path);
            FileInfo[] fi = di.GetFiles("*.*");

            int cnt_threads = fi.GetLength(0);
            for (int i = 0; i < fi.GetLength(0); i++)
            {
                ThreadPool.QueueUserWorkItem(delegate(object param)
                {
                    string _full_filename_csv = (string)param;
                    CSV_Core current_csv_core = new CSV_Core(_full_filename_csv, Config.csv_field_seperator, Config.csv_decimal_thousand_seperator, Config.csv_cnt_columns, Config.csv_symbol_column, Config.csv_date_column, Config.csv_data_column);

                    this.list_all_csv_cores.Add(current_csv_core);

                    this._file_added(current_csv_core);

                    if (Interlocked.Decrement(ref cnt_threads) == 0) mre_done.Set();
                        
                }, fi[i].FullName);
            }

            mre_done.WaitOne();
        }

private void bgw_set_files_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this._files_added();
        }

private void _file_added(CSV_Core csv_core)
        {
            if (dgv_csv.InvokeRequired) dgv_csv.Invoke(new MethodInvoker(delegate { _file_added(csv_core); }));
            else
            {
                dgv_csv.Rows.Add(false, csv_core.symbol_name, null, csv_core.first_date, csv_core.last_date);
            }
        }

        private void _files_added()
        {
            dgv_csv.AutoResizeColumns();
            dgv_csv.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            dgv_csv.MultiSelect = false;
            dgv_csv.Sort(dgv_csv.Columns["Symbol"], ListSortDirection.Ascending);
            dgv_csv[00].Selected = true;
            dgv_csv.Rows[0].Selected = false;
        }
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Di 28.04.09 19:40 
user profile iconfuncry hat folgendes geschrieben Zum zitierten Posting springen:
Ich habe deshalb gezögert, weil ich es auch als elegant empfunden hätte, wenn die GUI sich, mit eher komplizierten Dingen wie Threading, nicht beschäftigten müsste, sonder einfach eine Klasse nutzen könnte, welche dies erledigt.
Muss sie auch nicht, nur die Synchronisierung muss sie übernehmen - oder eben doch SynchronizationContext.
user profile iconfuncry hat folgendes geschrieben Zum zitierten Posting springen:
Da ich ja auch auf die Großschreibung bei Variablen verzichte,[...]
Alle Style-Guides schreiben für lokale Variablen camelCase vor.
So, ich versuche mich mal am Code. Ich nehme an, der Bottleneck ist der CsvCore-Konstruktor?

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
        CsvAggregator aggregator = new CsvAggregator(); // oder wie auch immer ;)

...
            aggregator.CoreRead += (sender, e) => BeginInvoke((Action) () => AddCore(e.Core));
            aggregator.ReadCompleted += (sender, e) => BeginInvoke((Action)FinishHim);

...
        private void CsvView_Load(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(ignore => aggregator.ReadDirectory(Config.CsvPath));
        }

        private void AddCore(CsvCore core);
        private void FinishHim();
...

    class CsvAggregator
    {
        public event EventHandler ReadCompleted;
        public event EventHandler<CoreReadEventArgs> CoreRead; // oder auch einfach Action<CsvCore>, wenn's auch nicht ganz den Guidelines entspricht

        public void ReadDirectoryAsync(string path)
        {
            string[] files = Directory.GetFiles(path);
            int threadCount = files.Length; // GetLength(0) ist etwas umständlich ;)
            foreach (string file in files)
                ThreadPool.QueueUserWorkItem(delegate
                {
                    CsvCore core = new CsvCore(file, Config); // 6 Parameter? Uff!
                    OnCoreRead(core);

                    if (Interlocked.Decrement(ref threadCount) == 0)
                        OnReadCompleted();
                });
        }
    }

Ich habe mal das eigentliche Liste-Führen der CsvCores in die GUI verschoben, um mich nicht mit der Race Condition befassen zu müssen, was im Kontext ganz ok oder völlig unpassend sein könnte ;) . Und zwei verschachtelte Lambdas könnten etwas übertrieben sein, aber so hat man die Synchronisierung an einem Platz.

_________________
>λ=
funcry Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 110
Erhaltene Danke: 1

Win7 64, XP 32
C# (VS 2010 EE), Delphi (TD 2006 Win32)
BeitragVerfasst: Mi 29.04.09 16:03 
Vielen Dank!

Zitat:
Ich nehme an, der Bottleneck ist der CsvCore-Konstruktor?

Richtig.

Damit ist der Grundstein gelegt, ähnlich zu dieser Klasse sind andere Klassen in meinem Projekt die teilweise umfangreicher sind, weshalb ich besonders froh bin, anhand dieses Falles, eine best practice für die Organisation des Codes zu haben :-) !