| Autor |
Beitrag |
funcry
      
Beiträge: 110
Erhaltene Danke: 1
Win7 64, XP 32
C# (VS 2010 EE), Delphi (TD 2006 Win32)
|
Verfasst: So 21.06.09 10:32
Ich nähere mich gerade dem Thema Exceptions, und habe festgestellt, dass es bei Threads Besonderheiten gibt. Nach der Durchsicht mancher MSDN Dokumente muss ich feststellen, daß ich dabei nicht alles verstanden habe.
Mein Problem ist, ich habe eine nicht-GUI Klasse welche Threaded ausgeführt wird. Dabei kann es durchaus zu Exceptions kommen. Durch Test habe ich herausgefunden, daß diese nicht aus dem jeweiligen Thread herausgeleitet werden. Meine Überlegung ist, daß die außerste aufrufende Klasse, welche immer eine GUI-Klasse ist, die Exception kennen sollte, diese dem User anzeigen sollte, und hier die Entscheidung getroffen werden sollte, wie die Anwendung fortfahren kann.
Beispiel:
Es geht um das Einlesen und Interpretieren(!) von CSV-Dateien. Möglicherweise hat der Anwender einen falschen CSV-Seperator eingegeben, dies führt zu einer Exception in der einlesenden nicht GUI-Klasse.
Quelltext-Beispiel:
Ich habe zur Veranschaulichung zwei try catch-Blöcke eingebaut. Dabei ist bei einem Fehler im Konstruktor von Samples folgendes zu beobachten:
Tritt im Konstruktor von Samples eine Exception auf, wird diese im inneren catch-Block angezeigt, jedoch nicht an den außeren Catch-Block weitergeleitet. Wie gesagt, ich könnte Events definieren (z.B. ReadErrorOccurred, ConvertErrorOccurred) und damit die Exceptions nach außen führen - aber ist das der richtige Weg ?
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:
| public void CreateSamplesAsync(CsvAggregator csvAggregator, SettingsTeach settingsTeach, string guidStr) { OnSampleCreationBegin(EventArgs.Empty);
int threadCount = csvAggregator.SelectedCsvCores.Count; if (threadCount > 0) { for (int i = 0; i < csvAggregator.SelectedCsvCores.Count; i++) { CsvCore csvCore = csvAggregator.SelectedCsvCores[i];
try { ThreadPool.QueueUserWorkItem(ignore => { try { OnSampleCreationBeginThread(csvCore);
for (int j = 0; j < settingsTeach.ParallelInstances; j++) { DBAccess.SaveSamples(csvCore.Symbol, 0, j, guidStr, new Samples(settingsTeach.DateTeachFrom, settingsTeach.DateTeachTo, false, csvCore, settingsTeach.PrevDaysCount, settingsTeach.ForecastDaysCount)); DBAccess.SaveSamples(csvCore.Symbol, 1, j, guidStr, new Samples(settingsTeach.DateCheckFrom, settingsTeach.DateCheckTo, true, csvCore, settingsTeach.PrevDaysCount, settingsTeach.ForecastDaysCount)); }
OnSampleCreatedThread(csvCore);
if (Interlocked.Decrement(ref threadCount) == 0) OnSampleCreationCompleted(); } catch { throw; } }); } catch { throw; } } } else OnSampleCreationCompleted(); } |
|
|
gfoidl
      
Beiträge: 157
Erhaltene Danke: 19
Win XP
C#, Fortran 95 - Visual Studio
|
Verfasst: So 21.06.09 11:07
Hallo,
um Exceptions in Threads an den Aufrufer weiterzuleiten kann wie in folgendem Beispiel vorgegangen werden. Ich verwende meist diesen Weg obwohl es noch eine andere Möglichkeit gibt -> siehe unten.
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: 59: 60: 61:
| using System; using System.Threading;
namespace Thread_und_Exception { class Program { private static readonly object _locker = new object(); private static Exception _threadException; private static AutoResetEvent _signal = new AutoResetEvent(false); static void Main(string[] args) { Console.WriteLine("Thread starten...");
try { ThreadPool.QueueUserWorkItem(MacheFehler); _signal.WaitOne();
if (_threadException != null) throw _threadException; } catch (Exception ex) { Console.WriteLine(ex); }
Console.WriteLine("Warten bis Thread fertig ist."); Console.WriteLine("Ende."); Console.ReadKey(); } public static void MacheFehler(object o) { try { Console.WriteLine("Im Thread.");
throw new Exception("Fehler im Thread.");
Console.WriteLine("Thread Ende."); } catch (Exception ex) { lock (_locker) { _threadException = new Exception(ex.Message, ex); } }
_signal.Set(); } } } |
Werden mehrere Threads verwendet kann für "_threadException" aus obigen Beispiel eine List<Exception> verwendet werden der die Fehler hinzugefügt werden.
Eine andere Möglichkeit existiert bei WinForm-Projekten bzw. wenn auf System.Windows.Forms.dll verwiesen wird.
Die Application-Klasse bietet ein ThreadException-Ereignis.
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| [STAThread] static void Main() { Application.ThreadException += (sender, e) => MessageBox.Show(e.Exception.ToString());
Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } |
Dies sollte aber nur als Notlösung verwendet werden. Siehe auch dotnet-snippets.de/d...-winform-SID983.aspx
mfG Gü
_________________ Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: So 21.06.09 11:33
|
|
gfoidl
      
Beiträge: 157
Erhaltene Danke: 19
Win XP
C#, Fortran 95 - Visual Studio
|
Verfasst: So 21.06.09 11:47
Kha hat folgendes geschrieben : | Wenn man den Main-Thread durch WaitOne anhält, wird die ganze Threading-Geschichte etwas sinnlos ... |
Wenn der ThreadPool verwendet wird muss irgendwann (bevor die Anwenund zu Ende ist) gewartet werden da es ein Hintergrundthread ist welcher sonst abgebrochen werden würde.
Die Taks aus Parallel-Extension funktionieren genau so - wie sonst auch. Macht also schon sinn - kommt halt darauf auf was man will.
mfG Gü
_________________ Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!
|
|
funcry 
      
Beiträge: 110
Erhaltene Danke: 1
Win7 64, XP 32
C# (VS 2010 EE), Delphi (TD 2006 Win32)
|
Verfasst: So 21.06.09 12:21
Danke für die Antworten! Aber ich stehe derzeit noch auf dem Schlauch.
Was die Sache komplizierter gestaltet, ist daß die Methode:
C#-Quelltext 1:
| public void CreateSamplesAsync(CsvAggregator csvAggregator, SettingsTeach settingsTeach, string guidStr) |
Aus der nicht-GUI Klasse SampleAggregator stammt.
Der Aufruf dieser Methode erfolgt erst aus der GUI Klasse:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| public partial class UITeachDgv : Form { [...]
private void buttonTeach_Click(object send, EventArgs ea) { [...]
ThreadPool.QueueUserWorkItem(ignore => sampleAggregator.CreateSamplesAsync(this.csvAggregator, this.settingsTeach, this.guidStr)); } } |
Wie müsste ich diesen Ansatz:
C#-Quelltext 1: 2: 3: 4: 5:
| try { Invoke(HandleFooResult, NonGuiClass.Foo(...)); } catch (Exception e) { Invoke(HandleFooException, e); } |
umsetzen ?
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: So 21.06.09 15:16
Zuletzt bearbeitet von Kha am So 21.06.09 16:36, insgesamt 2-mal bearbeitet
|
|
gfoidl
      
Beiträge: 157
Erhaltene Danke: 19
Win XP
C#, Fortran 95 - Visual Studio
|
Verfasst: So 21.06.09 15:23
Kha hat folgendes geschrieben : | Wenn man den Main-Thread durch WaitOne anhält, wird die ganze Threading-Geschichte etwas sinnlos ... |
Das mit dem Beispiel hast du wohl nicht kapiert - ist aber egal.
mfG Gü
_________________ Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: So 21.06.09 16:34
gfoidl hat folgendes geschrieben : | Das mit dem Beispiel hast du wohl nicht kapiert - ist aber egal. |
Wenn sich ein Beispiel nicht auf den Kontext (Winforms) übertragen lässt, stellt sich mir eben die Frage nach dem Sinn dahinter...
Natürlich ließe sich wenigstens der Lock und das Überträger-Feld auch in Winforms umsetzen, aber da funcry ja sicher bereits Invoke zur Synchronisierung benutzt, gibt es keinen Grund dazu.
Ich würde mich aber freuen, wenn du meinen PFX-Ansatz einschätzen würdest, da du dich ja mit PFX anscheinend schon auseinandergesetzt hast. Das war nur eine fixe Idee, zu der ich gern andere Meinungen hören würde.
_________________ >λ=
|
|
gfoidl
      
Beiträge: 157
Erhaltene Danke: 19
Win XP
C#, Fortran 95 - Visual Studio
|
Verfasst: So 21.06.09 17:27
Zurück zum ursprünglichen Problem:
Dabei geht es darum dass jeder Thread seinen eigenen Stacktrace hat. Somit ist nicht ohne weiteres möglich eine Exception in einem Thread an den Aufrufer weiterzuleiten.
Mein Beispiel zeigt beispielhaft (  ) wie die Exception des Threads in eine Exception des Aufrufers übergeführt wird. Werden mehrere Threads verwendet so kann eine List<Exception> verwendet werden um für jeden Thread die Exception weiter zu verarbeiten.
Die Verwendung von Tasks aus dem PFX ist prinzipiell nichts anderes als die Verwendung des ThreadPool nur dass das PFX einige benutzerfreundliche Erweiterungen besitzt. Das Exception-Handling funktioniert genauso wie in meinem Beispiel beschrieben.
Somit ist es auch egal ob ich auf einen Task warte oder auf einen Thread (ist ja das "Gleiche"). Irgendwo muss synchronsiert werden, außer der "Auftrag" ist nicht wichtig und kann terminiert werden sobald das Programm (vorzeigt) beendet wird.
Wer das Beispiel nicht auf den Kontext (Winforms) übertragen kann versteht nicht was beim Multithreading vor sich geht.
Im Beispiel von funcry müsste also der innere try-catch-Block wie beispielhaft gezeigt geändert werden.Im Ereignishandler "SampleCreationCompleted" wird dann geprüft ob ein Fehler aufgetreten ist.
Das Auslösen des "SampleCreationCompleted"-Ereignis stellt eine Alternative dar die Threads sozusagen zu synchronsieren bzw. exakter über das Ende der Thread-Verarbeitung informiert zu werden.
Den gezeigten PFX-Ansatz kann ich nicht nachvollziehen. Ich sehe jedoch keinen Grund dies so umzusetzten da obige Ausführung für mich transparenter ist.
mfG Gü
PS: Meine Ausführungen des PFX beziehen sich auf die CTP (glaube June 2008).
_________________ Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!
|
|
funcry 
      
Beiträge: 110
Erhaltene Danke: 1
Win7 64, XP 32
C# (VS 2010 EE), Delphi (TD 2006 Win32)
|
Verfasst: So 21.06.09 23:14
Danke euch beiden für die ausführliche Erklärung und die gezeigten Lösungsansätze!
|
|
|