title: WPF DataGrid Multithreading Crash
body: <p>I have a View with a <code>DataGrid</code> in it.
  A ViewModel as <code>DataContext</code> where i can access a <code>DataTable</code> in a background object.
  The background object has to work with the <code>DataTable</code> and keep it updated.
  The user has also be allowed to make changes to that <code>DataTable</code>.</p>
  
  <p>If i create a copy of the <code>DataTable</code> it stops crashing but the user is obviousely not working on the data.</p>
  
  <p>If i leave access open for the user the program crashed inevitabely.</p>
  
  <p>Here is a short program that will crash:</p>
  
  <p>app.cs</p>
  
  <pre><code>public partial class App : Application
  {
      public App()
      {
          SomeBackgroundThing background = new SomeBackgroundThing();
          MainWindowViewModel viewmodel = new MainWindowViewModel(background);
          MainWindowView view = new MainWindowView(viewmodel);
          view.Show();
      }
  }
  </code></pre>
  
  <p>main xaml</p>
  
  <pre><code>&lt;Window x:Class="NullPointerDataGrid.MainWindowView"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          Title="MainWindow" Height="350" Width="525"&gt;
      &lt;Grid&gt;
          &lt;DataGrid Name="datagrid" ItemsSource="{Binding Path=table, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/&gt;
      &lt;/Grid&gt;
  &lt;/Window&gt;
  </code></pre>
  
  <p>and the program code:</p>
  
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Windows;
  using System.Windows.Controls;
  using System.Windows.Documents;
  using System.Windows.Media;
  using System.Data;
  using System.Diagnostics;
  using System.Timers;
  using System.ComponentModel;
  
  namespace NullPointerDataGrid
  {
      public partial class MainWindowView : Window
      {
          public MainWindowView(MainWindowViewModel model)
          {
              DataContext = model;
              InitializeComponent();
              datagrid.Loaded += new RoutedEventHandler(ScrollToBottom);
              datagrid.AutoGeneratedColumns += new EventHandler(StarSizeLastRow);
  
          }
  
      void ScrollToBottom(object sender, RoutedEventArgs e)
      {
          Debug.WriteLine("TableGrid_ScrollToBottom");
          if (datagrid.Items.Count &gt; 0)
          {
              var border = VisualTreeHelper.GetChild(datagrid, 0) as Decorator;
              if (border != null)
              {
                  var scroll = border.Child as ScrollViewer;
                  if (scroll != null) scroll.ScrollToEnd();
              }
          }
  
      }
  
      void StarSizeLastRow(object sender, EventArgs e)
      {
          Debug.WriteLine("TableGrid_StarSizeLastColumn");
          try
          {
              datagrid.Columns[datagrid.Columns.Count - 1].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
          }
          catch { }
      }
  
      }
  
  public class MainWindowViewModel : INotifyPropertyChanged
  {
      public event PropertyChangedEventHandler PropertyChanged;
  
      private SomeBackgroundThing thing;
      public DataTable table
      {
          get
          {
              lock (thing.table)
              {
                  //DataTable wpfcopy = thing.table.Copy();
                  return thing.table;
              };
          }
          set
          {
              Debug.Write("This never happens");
          }
      }
  
      public MainWindowViewModel(SomeBackgroundThing thing)
      {
          this.thing = thing;
          thing.Changed += new EventHandler(thing_Changed);
      }
  
      void thing_Changed(object sender, EventArgs e)
      {
          if (PropertyChanged != null)
          {
              PropertyChanged(this, new PropertyChangedEventArgs("table"));
          }
      }
  }
  
  public class SomeBackgroundThing : IDisposable
  {
      public DataTable table;
      private DataTable tablecopy;
      private System.Timers.Timer timer, slowrowchanger;
  
      public event EventHandler Changed = new EventHandler((o, e) =&gt; { ;});
      protected void CallChanged(object sender, EventArgs e)
      {
          Changed(sender, e);
      }
  
      public SomeBackgroundThing()
      {
          CreateTable();
          UpdateB(this, null);
          tablecopy = table.Copy();
          InitAndStartTimer(1);
      }
  
      #region timer
  
      private void UpdateA()
      {
          Boolean haschanged = false;
          DataTable newcopy = table.Copy(); ;
          if (newcopy.Rows.Count != tablecopy.Rows.Count)
          {
              Debug.WriteLine("Different ammount of rows");
              haschanged = true;
          }
          else if (newcopy.Columns.Count != tablecopy.Columns.Count)
          {
              Debug.WriteLine("Different ammount of columns");
              haschanged = true;
          }
          else
          {
              for (int i = 0; i &lt; newcopy.Rows.Count; i++)
              {
                  for (int j = 0; j &lt; newcopy.Columns.Count; j++)
                  {
                      if (newcopy.Rows[i][j].ToString() != tablecopy.Rows[i][j].ToString())
                      {
                          Debug.WriteLine(String.Format(
                              "Element [{0}/{1}]: {2} is different from {3}",
                              i, j, newcopy.Rows[i][j], tablecopy.Rows[i][j]
                              ));
                          haschanged = true;
                      }
                      if (haschanged) break;
                  }
                  if (haschanged) break;
              }
          }
          if (haschanged)
          {
              tablecopy = newcopy;
          }
      }
  
      private void InitAndStartTimer(int interval)
      {
          timer = new System.Timers.Timer();
          timer.Interval = interval;
          timer.AutoReset = true;
          timer.Elapsed += new ElapsedEventHandler((s, e) =&gt;
          {
              UpdateA();
          });
          timer.Enabled = true;
  
          slowrowchanger = new System.Timers.Timer();
          slowrowchanger.Interval = 3000;
          slowrowchanger.AutoReset = true;
          slowrowchanger.Elapsed += new ElapsedEventHandler((s, e) =&gt;
          {
              UpdateB(null, null);
          });
          slowrowchanger.Enabled = true;
  
      }
  
      public void Dispose()
      {
          timer.Enabled = false;
          slowrowchanger.Enabled = false;
          timer.Dispose();
          slowrowchanger.Dispose();
      }
  
      #endregion
  
      #region editlastrow
  
      void UpdateB(object sender, EventArgs e)
      {
          Random rnd = new Random();
          List&lt;String&gt; cells = new List&lt;string&gt;{
                  "The SAME", 
                  rnd.Next(0,100).ToString(), 
                  rnd.ToString(), 
                  rnd.NextDouble().ToString()};
          lock (table)
          {
              OverwriteOrAppendLastRow(ref table, cells);
              table.AcceptChanges();
          }
          CallChanged(this, null);
      }
  
      private void OverwriteOrAppendLastRow(ref DataTable table, List&lt;string&gt; newrow)
      {
          if (table.Rows.Count == 0) CreteEmptyRow(ref table);
          if (newrow[0].ToString() != table.Rows[table.Rows.Count - 1][0].ToString())
          {
              Debug.WriteLine(String.Format("Creating row because '{0}' is different from '{1}'", newrow[0], table.Rows[table.Rows.Count - 1][0]));
              CreteEmptyRow(ref table);
          }
          OverwriteLastRow(ref table, newrow);
      }
  
      private void OverwriteLastRow(ref DataTable table, List&lt;string&gt; newrow)
      {
          for (int i = 0; i &lt; newrow.Count() &amp;&amp; i &lt; table.Columns.Count; i++)
          {
              table.Rows[table.Rows.Count - 1][i] = newrow[i];
          }
      }
  
      private void CreteEmptyRow(ref DataTable table)
      {
          table.Rows.Add(new String[table.Columns.Count]);
      }
  
      #endregion
  
      private void CreateTable()
      {
          table = new DataTable();
          table.Columns.Add("FirstCell", typeof(String));
          table.Columns.Add("BananaCell", typeof(String));
          table.Columns.Add("CherryCell", typeof(String));
          table.Columns.Add("Blue", typeof(String));
          Random rnd = new Random();
          for (int i = 0; i &lt; 145; i++)
          {
              table.Rows.Add(new String[]{
                  rnd.Next().ToString(), 
                  rnd.Next(0,i+1).ToString(), 
                  rnd.ToString(), 
                  rnd.NextDouble().ToString()});
          }
      }
  
  }
  
  }
  </code></pre>
  
  <p>How can i stop this multithread crashing?</p>
  
  <hr>
  
  <p>EDIT:</p>
  
  <p>I don't know if there are more than one reasons for this code to crash. But i did my best to gather some information about one reason to crash:</p>
  
  <p>Nullpointer exception in App.g.cs - the autogenerated portion. The Debugger wont step into it - so i cant say anything about the line it crashes in.</p>
  
  <p>Here is the Exception Detail, sorry for the German.</p>
  
  <pre><code>System.NullReferenceException wurde nicht behandelt.
    Message=Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
    Source=PresentationFramework
    InnerException: 
  </code></pre>
  
  <p>The Stacktrace only shows "Externer Code" so no stack to trace.</p>
  
  <p>The thing is that WPF crashes - my code can handle it... somehow i need to capsule WPF so it wont crash, one way to do that is to copy the DataTable - but then i loose the ability to write back that table since its setter is not called when something has gotten edited.</p>
  
  <p>EDIT #2:</p>
  
  <p>I recreated this example to show the error i have in another program and i just found out that what crashes is actually related with the scrollbar. If i change the ammount of displayed data to a low number so that there is no scrollbar, the code will not crash.</p>