113

I have an application which updates my datagrid each time a log file that I'm watching gets updated (Appended with new text) in the following manner:

private void DGAddRow(string name, FunctionType ft)
    {
                ASCIIEncoding ascii = new ASCIIEncoding();

    CommDGDataSource ds = new CommDGDataSource();

    int position = 0;
    string[] data_split = ft.Data.Split(' ');
    foreach (AttributeType at in ft.Types)
    {
        if (at.IsAddress)
        {

            ds.Source = HexString2Ascii(data_split[position]);
            ds.Destination = HexString2Ascii(data_split[position+1]);
            break;
        }
        else
        {
            position += at.Size;
        }
    }
    ds.Protocol = name;
    ds.Number = rowCount;
    ds.Data = ft.Data;
    ds.Time = ft.Time;

    dataGridRows.Add(ds); 

    rowCount++;
    }
    ...
    private void FileSystemWatcher()
    {
        FileSystemWatcher watcher = new FileSystemWatcher(Environment.CurrentDirectory);
        watcher.Filter = syslogPath;
        watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
            | NotifyFilters.FileName | NotifyFilters.DirectoryName;
        watcher.Changed += new FileSystemEventHandler(watcher_Changed);
        watcher.EnableRaisingEvents = true;
    }

    private void watcher_Changed(object sender, FileSystemEventArgs e)
    {
        if (File.Exists(syslogPath))
        {
            string line = GetLine(syslogPath,currentLine);
            foreach (CommRuleParser crp in crpList)
            {
                FunctionType ft = new FunctionType();
                if (crp.ParseLine(line, out ft))
                {
                    DGAddRow(crp.Protocol, ft);
                }
            }
            currentLine++;
        }
        else
            MessageBox.Show(UIConstant.COMM_SYSLOG_NON_EXIST_WARNING);
    }

When the event is raised for the FileWatcher, because it creates a separate thread, when I try to run dataGridRows.Add(ds); to add the new row, the program just crashes without any warning given during debug mode.

In Winforms, this was easily solved by utilizing the Invoke function but I am not sure how to go about this in WPF.

l46kok
  • 6,562
  • 34
  • 98
  • 171

3 Answers3

243

You can use

Dispatcher.Invoke(Delegate, object[])

on the Application's (or any UIElement's) dispatcher.

You can use it for example like this:

Application.Current.Dispatcher.Invoke(new Action(() => { /* Your code here */ }));

or

someControl.Dispatcher.Invoke(new Action(() => { /* Your code here */ }));
Botz3000
  • 38,118
  • 8
  • 102
  • 126
  • The above approach was giving an error because Application.Current is null at the time of running the line. Why would this be the case? – l46kok Jul 24 '12 at 06:39
  • You can just use any UIElement for that, since every UIElement has the "Dispatcher" property. – Wolfgang Ziegler Jul 24 '12 at 06:40
  • 1
    @l46kok This can have different reasons (console app, hosting from winforms etc.). As @WolfgangZiegler said, you can use any UIElement for it. I just usually use `Application.Current` for it since it looks cleaner to me. – Botz3000 Jul 24 '12 at 06:51
  • @Botz3000 I think I also have some race condition problem happening here. After appending the code given above, the code works perfectly when I go into the debug mode and manually do stepovers, but the code crashes when I run the application without debug. I am not sure what to lock here that is causing a problem. – l46kok Jul 24 '12 at 06:54
  • 1
    @l46kok If you think it's a deadlock, you can also call `Dispatcher.BeginInvoke`. That method just queues the delegate for execution. – Botz3000 Jul 24 '12 at 06:58
  • @Botz3000 Nope. Doesn't seem like it's a deadlock. The program still crashes :( – l46kok Jul 24 '12 at 07:04
  • @l46kok have you checked dataGridRows for null? Maybe the code is running before the window is completely initialized? – Botz3000 Jul 24 '12 at 07:31
  • Nah looks like it was a different issue. I got it solved. Thanks. – l46kok Jul 24 '12 at 08:00
  • Brilliant solution! Works like a charm for me. However, I would add this `DispatcherPriority.Normal` to prevent any unexpected results. Without it, as an example, my program throws unhandled exceptions regarding task canceled by the user and it crashes the program. – B.K. Dec 05 '13 at 07:38
59

The best way to go about it would be to get a SynchronizationContext from the UI thread and use it. This class abstracts marshalling calls to other threads, and makes testing easier (in contrast to using WPF's Dispatcher directly). For example:

class MyViewModel
{
    private readonly SynchronizationContext _syncContext;

    public MyViewModel()
    {
        // we assume this ctor is called from the UI thread!
        _syncContext = SynchronizationContext.Current;
    }

    // ...

    private void watcher_Changed(object sender, FileSystemEventArgs e)
    {
         _syncContext.Post(o => DGAddRow(crp.Protocol, ft), null);
    }
}
Eli Arbel
  • 21,775
  • 3
  • 42
  • 70
  • Thanks a lot! The accepted solution started hanging every time it was called, but this works. – Dov Nov 26 '13 at 21:20
  • It also works when called from an assembly containing the view model but no "real" WPF, i.e. is a class library. – Onur Apr 28 '15 at 16:38
  • This is a very useful tip, specially when you have a non-wpf component with a thread that you want to marshal actions to. of course another way to do it would be to use TPL continuations – MaYaN May 29 '15 at 13:19
  • I didnt understand it at first, but it worked for me.. nice one. (Should point out DGAddRow is a private method) – Tim Davis Mar 26 '18 at 18:12
  • 3
    You should mention that `_syncContext.Post(o =>` is an asynchronous "fire and forgett". To make a synchronous call `_syncContext.Send(o =>` should be used. – marsh-wiggle Nov 22 '20 at 09:30
7

Use [Dispatcher.Invoke(DispatcherPriority, Delegate)] to change the UI from another thread or from background.

Step 1. Use the following namespaces

using System.Windows;
using System.Threading;
using System.Windows.Threading;

Step 2. Put the following line where you need to update UI

Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate
{
    //Update UI here
}));

Syntax

[BrowsableAttribute(false)]
public object Invoke(
  DispatcherPriority priority,
  Delegate method
)

Parameters

priority

Type: System.Windows.Threading.DispatcherPriority

The priority, relative to the other pending operations in the Dispatcher event queue, the specified method is invoked.

method

Type: System.Delegate

A delegate to a method that takes no arguments, which is pushed onto the Dispatcher event queue.

Return Value

Type: System.Object

The return value from the delegate being invoked or null if the delegate has no return value.

Version Information

Available since .NET Framework 3.0

Vineet Choudhary
  • 7,099
  • 3
  • 46
  • 71