Laden...

Multithreading-Problem

Erstellt von Joltan vor 19 Jahren Letzter Beitrag vor 19 Jahren 2.347 Views
J
Joltan Themenstarter:in
58 Beiträge seit 2004
vor 19 Jahren
Multithreading-Problem

Ich habe eine DataTable-Klasse welche neben verschiedener Methoden auch eine Arraylist-basierte Klasse enthält. Eine der Methoden ist nun dafür da regelmäßig die Update()-Methode der Objekte in der Arraylist aufzurufen und dann den DataTable entsprechend upzudaten. Wenn ich das ohne Multithreading mache, dann klapp das auch ganz fein, aber sobald ich versuche einen seperaten Update-Thread zu benutzen bekomme ich Ärger:

Illegal cross-thread operation: Control '' accessed from a thread other than the thread it was created on.\r\nStack trace where the illegal operation occurred was:\r\n\r\n\tat System.Windows.Forms.Control.get_Handle()\r\n\tat System.Windows.Forms.Control.SetVisibleCore(Boolean)\r\n\tat System.Windows.Forms.Control.set_Visible(Boolean)\r\n\tat System.Windows.Forms.DataGridView.LayoutScrollBars()\r\n\tat System.Windows.Forms.DataGridView.ComputeLayout()\r\n\tat System.Windows.Forms.DataGridView.PerformLayoutPrivate(Boolean, Boolean)\r\n\tat System.Windows.Forms.DataGridView.ResetUIState(Boolean, Boolean)\r\n\tat System.Windows.Forms.DataGridViewRowCollection.OnCollectionChanged_PreNotification(CollectionChangeAction, Int32, Int32, DataGridViewRow, Boolean, Point)\r\n\tat System.Windows.Forms.DataGridViewRowCollection.OnCollectionChanged(CollectionChangeEventArgs, Int32, Int32, Boolean, Boolean, Boolean, Point)\r\n\tat System.Windows.Forms.DataGridViewRowCollection.RemoveAtInternal(Int32, Boolean)\r\n\tat System.Windows.Forms.DataGridView.DataGridViewDataConnection.ProcessListChanged(ListChangedEventArgs)\r\n\tat System.Windows.Forms.DataGridView.DataGridViewDataConnection.currencyManager_ListChanged(Object,..."

Hier die relevanten Methoden der DataTable-Klasse:


private Thread MTUpdateThread;
private void MTUpdateMethod()
{
 int max = this.AddressIndex.Count;
 for (int i = 0; i < max; i++)
 {
  bool repeat = true;
  Loop:
  Thread.Sleep(500);
  if (Monitor.TryEnter(this))
  {
   ServerClass server = (ServerClass)this.ServerList[i];
   server.Update();
   DataRow newrow = this.NewRow();
   newrow = this.Server2Row(server);
   this.Rows.RemoveAt(i);
   this.Rows.InsertAt(newrow, i);
   Monitor.Exit(this);
   repeat = false;
  }
  if (repeat) {goto Loop;};
 }
}
public void StartUpdate()
{
  this.MTUpdateThread = new Thread(new ThreadStart(MTUpdateMethod));
  this.MTUpdateThread.Start();
}
public void StopUpdate()
{
 if ((this.MTUpdateThread != null) && (this.MTUpdateThread.IsAlive))
 {
   this.MTUpdateThread.Abort();
   this.MTUpdateThread.Join();
 }
}

Der Fehler tritt immer in dem Moment auf, in dem der UpdateThread die folgende Zeile in der MTUpdateMethod() erreicht (also bevor der zu dieser Zeile gehörende Breakpoint erreicht wird, aber nach dem Breakpoint in der vorherigen):

this.Rows.InsertAt(newrow, i);

Hier noch der relevante Code der entsprechenden Form:


partial class Form1 : Form
{
 ServerTableClass ServerTable = new ServerTableClass();
 public Form1()
 {
  InitializeComponent();
  ServerTable.Load();
  if (ServerTable.Rows.Count == 0) { ServerTable.SetDefault(); }
  dataGridView1.DataSource = ServerTable;
  ServerTable.StartUpdate();
 }
}

Keine anderen Methoden außer dem DataGrid greifen auf die Klasse zu...

F
124 Beiträge seit 2004
vor 19 Jahren

also erstmal muss ich dir nochmal sagen, dass es illegal ist, elemente des ui-threads in einem anderen thread aufzurufen.

aber es gibt möglichkeiten

werf mal einen blick auf die Control.Invoke*() - Methoden

J
Joltan Themenstarter:in
58 Beiträge seit 2004
vor 19 Jahren

"nochmal" ?(

Naja, ich habe jetzt mal den Refresh des DataTable rausgenommen - jetzt werden zwar die in der ArrayList abgelegten Objekte schön im Hintergrund automatisch aktualisiert, der DataTable ist aber natürlich nicht mehr mit diesen synchronisiert. Dein Tip mit Invoke hat mich so ohne jede weitere Erklärungen leider nicht weitergebracht, da dies mein erster MT-Versuch ist und ich auch sonst noch nicht allzu firm in C# bin. Vieleicht kannst Du mir da etwas konkreter helfen?

Edit: DataTable hat scheinbar keine Invoke-Methode...

J
Joltan Themenstarter:in
58 Beiträge seit 2004
vor 19 Jahren

Habe mich jetzt etwas in die Materie eingelesen und muß leider feststellen, daß Invoke nur direkt mit Controls funktioniert - mein DataTable ist zwar an eine DataGridView gebunden, aber wie kann ich diese im Invoke-Aufruf (der aus einer seperaten Klasse heraus erfolgt in der das DataGridView nicht bekannt ist, sondern nur der entsprechende DataTable) ansprechen? Um ein Beispiel wäre ich sehr dankbar!

F
10.010 Beiträge seit 2004
vor 19 Jahren

Du musst einfach in deiner Form eine Funktion z.B. RowUpdate machen:


private void RowUpdate( DataRow newrow, int number)
{
  this.Rows.InsertAt(newrow, number); 
}

Dann brauchst Du zum aufrufen ein Delegate


delegate void DelRowUpdate( DataRow but, int number);

Und die kannst Du dann aus deinem Thread aufrufen mit Invoke


this.Invoke( new DelRowUpdate(RowUpdate), new Object[]{newrow,i} );

J
Joltan Themenstarter:in
58 Beiträge seit 2004
vor 19 Jahren

Danke, inzwschen hab ich's geschafft - der eigentliche Update-Thread ist innerhalb der DataTable-Klasse angelegt (die ich auch gleich nochmal sauber reimplementiert habe - war doch ein arges Gewurschtel vorher), den eigentlichen Refresh der DataGridView übernimmt aber ein weiterer Thread, welcher im Form angelegt ist. Da der Refresh-Thread keine Parameter übergibt konnte ich mir dabei den eigenen Delegate sparen und anstelle dessen MethodInvoker verwenden.

Die Foren hier sind wirklich Gold wert! 8) 👍
(nicht, daß ich welches hätte! 😄)

F
10.010 Beiträge seit 2004
vor 19 Jahren

Wir nehmen auch Kreditkartennummern oder PayPal 😉