120

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.

TtT23
  • 6,876
  • 34
  • 103
  • 174

3 Answers3

258

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
  • 39,020
  • 8
  • 103
  • 127
  • 1
    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? – TtT23 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. – TtT23 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 :( – TtT23 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. – TtT23 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
67

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);
    }
}

Note that using the Send method is usually not recommended - we'd be needlessly blocking a thread-pool thread waiting for the UI to update, rather than employing an async approach where we wait for UI updates using events (such as property and collection updates), and releasing that thread to do other work.

Eli Arbel
  • 22,391
  • 3
  • 45
  • 71
  • 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
  • 4
    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
  • 1
    Also Stephen Cleary in his book not recommend to use the Dispatcher since it platform-specific. He recommends `SynchronizationContext.Current` (see part 13.2). – Rodion Mostovoi Jun 13 '22 at 00:59
8

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,433
  • 4
  • 45
  • 72