2

I am writing an application with two parts, one part downloads data and lists its sources in a file which is being monitored by the other part which, every 15 minutes when the data is downloaded therefore updating the file, it loads the file contents and removes the old data. I currently have this code:

   private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
   {
       try
       {
           fsw.EnableRaisingEvents = false;
           MessageBox.Show("File Changed: " + e.FullPath);
           _times.Clear();

           XmlDocument dataset = new XmlDocument();
           dataset.Load(@"C:\Users\Henry\AppData\Local\{9EC23EFD-F1A4-4f85-B9E9-729CDE4EF4C7}\cache\DATA_RAINOBS\dataset.xml");
           for (int x = 0; x < dataset.SelectNodes("//Times/Time").Count; x++)
           {
               _times.Add(
                   new Time()
                   {
                       Original = dataset.SelectNodes("//Times/Time/@original")[x].InnerText,
                       Display = dataset.SelectNodes("//Times/Time/@display")[x].InnerText,
                       Directory = dataset.SelectNodes("//Times/Time/@directory")[x].InnerText + "_LORES.png"
                   });
           }
           times.SelectedIndex = 0;
       }
       finally { fsw.EnableRaisingEvents = true; }
   }

But when I run it, I get a System.NotSupportedException and from further information I know that it is because I am trying to manipulate a list from a separate thread created by the FileSystemWatcher.

I have literally done hardly any programming using multiple threads so I have no idea what to do. It would be very helpful if someone could modify the code above so that it is thread safe and will work properly because then I will have something to learn from and it won't be wrong. How do I make this work?

Henry Hunt
  • 181
  • 5
  • 12
  • What line causes the exception? – Emond Feb 13 '14 at 15:59
  • The line highlighted in the error is the creation of the XmlDocument: XmlDocument dataset = new XmlDocument(); and the error is: this type of collectionview does not support changes to its sourcecollection from a different thread – Henry Hunt Feb 13 '14 at 16:01
  • @HenryHunt - Post the actual exception message here which you getting. – Rohit Vats Feb 13 '14 at 16:29
  • @RohitVats An unhandled exception of type 'System.NotSupportedException' occurred in PresentationFramework.dll Additional information: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread. – Henry Hunt Feb 13 '14 at 16:32
  • @HenryHunt - I have posted an answer. Please check. – Rohit Vats Feb 13 '14 at 16:43

2 Answers2

1

You have to use Invoke on the control (or its owner). The method will then be queued for processing on the control's thread, rather than the FSW's thread.

On WPF, this is handled by a dispatcher for this, eg.

times.Dispatcher.Invoke(() => { yourCode; });

If you're expecting your code to take some time, you might consider only doing the invoke with a full list of items at the end, rather than invoking the whole operation.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • And in WinForms, you use Control.Invoke() http://www.dailycoding.com/Posts/using_invoke_to_access_object_in_a_windows_formscontrols_thread.aspx – Oscar Feb 13 '14 at 16:02
  • This will solve the problems I will have with the controls but the error is in the line creating the XmlDocument. Why is this? Is it not possible to create an XmlDocument on another thread? – Henry Hunt Feb 13 '14 at 16:05
  • @HenryHunt Are you sure? Isn't the error on the `times.Clear` instead? `MessageBox.Show` is also not a good idea in a different thread. – Luaan Feb 13 '14 at 16:08
  • @Luaan It could be on times.Clear() but times.Clear() is actually _times.Clear() and _times is a variable (List – Henry Hunt Feb 13 '14 at 16:13
  • @HenryHunt Ah, right... however, I don't see you setting the data source on the actual `times` control, is it already assigned? That could mean that changing `_times` would actually be changing the control as well. Just a thought. – Luaan Feb 13 '14 at 16:35
  • @Luaan The control is bound to the _times list so all this code is doing is changing the items in its binding. – Henry Hunt Feb 13 '14 at 16:37
  • @HenryHunt Yeah, which probably means that doing `_times.Clear();` will cause an action on the control, thus causing the exception. In any case, if you do all the manipulation inside of the `Invoke`, you should be just fine. – Luaan Feb 13 '14 at 16:42
0

Your _times collection is binded with GUI part so error must be at line

_times.Clear();

WPF has constraint that you cannot modify source collection which is binded to GUI from thread other than UI thread. Refer to my answer over here for details.

As stated above you can modify it from only UI thread so consider dispatching this stuff on UI dispatcher which will queue this on UI thread. Get UI dispatcher like this:

App.Current.Dispatcher.Invoke((Action)delegate
{
   _times.Clear();
});

Also make sure any subsequent calls related to UI is dispatched on UI thread. May be wrap it under one call only:

XmlDocument dataset = new XmlDocument();
dataset.Load(@"C:\Users\Henry\AppData\Local\{9EC23EFD-F1A4-4f85-B9E9-
               729CDE4EF4C7}\cache\DATA_RAINOBS\dataset.xml");
App.Current.Dispatcher.Invoke((Action)delegate
{
   _times.Clear();
   for (int x = 0; x < dataset.SelectNodes("//Times/Time").Count; x++)
   {
      _times.Add(
           new Time()
           {
               Original = dataset.SelectNodes("//Times/Time/@original")
                                [x].InnerText,
               Display = dataset.SelectNodes("//Times/Time/@display")
                                [x].InnerText,
               Directory = dataset.SelectNodes("//Times/Time/@directory")
                                [x].InnerText + "_LORES.png"
           });
   }
   _times.SelectedIndex = 0;
});
Community
  • 1
  • 1
Rohit Vats
  • 79,502
  • 12
  • 161
  • 185