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...
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
"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...
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!
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} );
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! 😄)