tags:

views:

44

answers:

1

I am having a weird problem with C#, I can't yet explain. I actually planned to create a simple file viewer that automatically updates the displayed text as soon as the file is changed, but it turns out more complicated than I thought.

I have the following basic code:

public partial class Main : Window
{
    private FileSystemWatcher watcher;
    private String filePath = "C:\\";
    private String fileName = "example.txt";

    public Main()
    {
        InitializeComponent();

        this.txtFile.Text = File.ReadAllText(filePath + fileName);

        this.watcher = new FileSystemWatcher(filePath);
        this.watcher.Changed += new FileSystemEventHandler(watcher_Changed);
        this.watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite;
        this.watcher.Filter = fileName;
        this.watcher.EnableRaisingEvents = true;
    }

    void watcher_Changed(object sender, FileSystemEventArgs e)
    {
        String text = File.ReadAllText(e.FullPath);
        this.txtFile.Text = text; // <-- here comes the exception (line 42)
    }
}

Now the assignment to the (WPF) TextField in the changed handler raises a System.InvalidOperationException exception, telling me that the object is already used in a different thread. So why do I get that exception and more importantly, what do I have to do, to make this working?

edit

By the way, I get the exception regardless of the string I'm assigning.

edit2

Full text of the exception, but in german:

System.InvalidOperationException wurde nicht behandelt.
  Message="Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet."
  Source="WindowsBase"
  StackTrace:
       bei System.Windows.Threading.Dispatcher.VerifyAccess()
       bei System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
       bei System.Windows.Controls.TextBox.set_Text(String value)
       bei LiveTextViewer.Main.watcher_Changed(Object sender, FileSystemEventArgs e) in ...\Main.xaml.cs:Zeile 42.
       bei System.IO.FileSystemWatcher.CompletionStatusChanged(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* overlappedPointer)
       bei System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
  InnerException:
+3  A: 

FileSystemWatcher is raising this event on a different thread. You can't access user interface elements from a different thread than the one on which it was created.

You'll need to use the Dispatcher to push the call that sets the text back onto the UI thread:

void watcher_Changed(object sender, FileSystemEventArgs e)
{
    String text = File.ReadAllText(e.FullPath);

    this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action) () => 
        {
             this.txtFile.Text = text;
             // Do all UI related work here...
        });
}
Reed Copsey
Thanks for your answer; maybe it's just me (or another missing import? Which would at least explain all the missing IntelliSense details for Invoke), but this doesn't work: It says that the lambda expression can't be converted into System.Delegate.
poke
@poke: Sorry - I forgot to put the cast to (Action) in place. Dispatcher.Invoke takes "System.Delegate",so you have to specify some concrete delegate type. The above should work now.
Reed Copsey
After putting another pair of parentheses around the lambda it indeed compiles and works, and also makes completely sense. Thank you very much :)
poke
@Reed: Now that .NET 4 has been out for a bit, I think it's time to start gently pushing people towards `Task` and away from `Dispatcher` and `ISynchronizeInvoke`. I recently [wrote a blog post](http://nitoprograms.blogspot.com/2010/06/reporting-progress-from-tasks.html) on the subject (which uses a nested `Task` to update the UI). What do you think?
Stephen Cleary
@Stephen: I would agree, in most cases - however, in order to use a Task here, you'd have to store a copy of a task scheduler or the synchronization context in the class. Otherwise, the code above would need to be refactored to use a lambda for the handler (which just changes the burden of storage to the compiler). I very much prefer using Tasks for new development, but it's not always as easy to interface with legacy components, especially ones already using callbacks or events.
Reed Copsey