I am having an issue with MonoTouch where UIViewControllers are remaining in-memory forever, even after they have been popped from the navigation stack.
I have a UINavigationController which contains a UIViewController with a button. Clicking the button pushes a custom UIViewController called ThreadingViewController onto the navigation stack.
The ThreadingViewController uses a NSTimer.CreateRepeatingScheduledTimer and a Thread to update the text of a label every one second.
When the user clicks "back" to pop back to the root view, the Mono Profiler says that my ThreadingViewController still exists in memory. The Profiler tells me it has something to do with NSAction and/or ThreadStart which has a reference to the ThreadingViewController, keeping it alive. I can see this by checking the "inverse references" checkbox in the profiler.
This means that if the user clicks backwards and forwards 100 times between the root ViewController and the custom ThreadingViewController, there will be 100 instances of this ViewController in memory. It isn't being garbage collected.
In ViewDidDisappear I have tried aborting the thread, setting it to null, to no avail.
What do I need to do to get this ThreadingViewController to be properly cleaned up / GC'ed by MonoTouch?
Here is the full (C# only) source code to reproduce the problem:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using System.Threading;
namespace MemoryTests
{
[Register("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
private UIWindow window;
private UINavigationController rootNavigationController;
private RootScreen rootScreen;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
rootScreen = new RootScreen();
rootNavigationController = new UINavigationController(rootScreen);
window.RootViewController = rootNavigationController;
window.MakeKeyAndVisible();
return true;
}
}
public class RootScreen : UIViewController
{
private UIButton button;
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.Title = "Root Screen";
// Add a button
button = new UIButton(UIButtonType.RoundedRect);
button.SetTitle("Click me", UIControlState.Normal);
button.Frame = new RectangleF(100, 100, 120, 44);
this.View.Add(button);
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
button.TouchUpInside += PushThreadingViewController;
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
button.TouchUpInside -= PushThreadingViewController;
}
private void PushThreadingViewController(object sender, EventArgs e)
{
var threadingViewController = new ThreadingViewController();
NavigationController.PushViewController(threadingViewController, true);
}
}
public class ThreadingViewController : UIViewController
{
private UILabel label;
private NSTimer timer;
private int counter;
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.Title = "Threading Screen";
// Add a label
label = new UILabel();
label.Frame = new RectangleF(0f, 200f, 320f, 44f);
label.Text = "Count: 0";
this.View.Add(label);
// Start a timer
var timerThread = new Thread(StartTimer as ThreadStart);
timerThread.Start();
}
public override void ViewDidDisappear (bool animated)
{
base.ViewDidDisappear(animated);
timer.Dispose();
timer = null;
// Do I need to clean up more Threading things here?
}
[Export("StartTimer")]
private void StartTimer()
{
using (var pool = new NSAutoreleasePool())
{
timer = NSTimer.CreateRepeatingScheduledTimer(1d, TimerTicked);
NSRunLoop.Current.Run();
}
}
private void TimerTicked()
{
InvokeOnMainThread(() => {
label.Text = "Count: " + counter;
counter++;
});
}
}
}
Here is a screenshot of the profiler telling me that we have 3 instances of ThreadingViewController in memory:
Cheers.
I think the problem is here:
timer.Dispose();
Try changing it to:
timer.Invalidate();
NSTimer is the only thing here that uses an NSAction.
Also, I don't think this is very helpful:
var timerThread = new Thread(StartTimer as ThreadStart);
timerThread.Start();
Reasons:
It's not a background thread. Not sure how foreground .NET threads behave on iOS. My guess is, it never finishes.
You don't need to have a separate thread live all the time, within from you are starting the NSTimer. NSTimers are light, bulletproof and provide all the options you need. They work great even if you are starting them on the UI thread.
(Please ignore #2 above if you have another reason you are doing it this way, which is not directly visible from your code. But, nothing else comes to mind).
PS: I haven't tested your code above. But I'm 100% sure that you must Invalidate() NSTimers so that they stop running, when you no longer need them.
Anytime you connect a click handler such as:
button.TouchUpInside += PushThreadingViewController;
You must also disconnect it. I would suggest moving the line of code above to the ViewDidAppear override, and adding the line of code below to the ViewDidDisappear override:
button.TouchUpInside -= PushThreadingViewController;
I would also recommend that you check to see if threadingViewController is null and only creating a new instance if it is, before pushing it onto the navigation stack. That way it will use the same one each time if it hasn't been garbage collected yet.
Be careful with ViewDidDisappear. This method may called at any time your View-Controller yields control to another View-Controller (calling PresentViewController, invoking the sharing UI, display an iAd etc). If you only want to react when your View-Controller is removed from the navigation stack, I'd recommend this:
public override void DidMoveToParentViewController(UIViewController parent)
{
base.DidMoveToParentViewController(parent);
if(parent == null)
Cleanup();
}
Try invoking ReleaseOutlets() (generated from the Xamarin Studio XIB processor) from within your Cleanup() method in addition to unwiring the touch event handler improves the situation.
Related
This is an incredibly simple task tray app - using ApplicationContext and a few guides I found online.
The purpose of the app is to query a small REST API and show a message box to the user on a given result. I need to essentially have the API query in a background loop, running every 10 seconds or something similar. This is to report on data that I've made accessible via another service.
I've done some reading and it seems a BackgroundWorker and Timer is an appropriate option, but I'm lost on where to go next. How exactly can I achieve this? I initially tried adding a while(true) loop to the TaskTrayApplicationContext but it just created an infinite loop whereby you couldn't do anything else with the app.
namespace TaskTrayApplication
{
public class TaskTrayApplicationContext : ApplicationContext
{
NotifyIcon notifyIcon = new NotifyIcon();
Configuration configWindow = new Configuration();
public TaskTrayApplicationContext()
{
MenuItem configMenuItem = new MenuItem("Configuration", new EventHandler(ShowConfig));
MenuItem exitMenuItem = new MenuItem("Exit", new EventHandler(Exit));
notifyIcon.Icon = TaskTrayApplication.Properties.Resources.AppIcon;
notifyIcon.DoubleClick += new EventHandler(ShowMessage);
notifyIcon.ContextMenu = new ContextMenu(new MenuItem[] { configMenuItem, exitMenuItem });
notifyIcon.Visible = true;
}
void ShowMessage(object sender, EventArgs e)
{
// Only show the message if the settings say we can.
if (TaskTrayApplication.Properties.Settings.Default.ShowMessage)
MessageBox.Show("This is the Serenity TaskTray Agent.");
}
void ShowConfig(object sender, EventArgs e)
{
// If we are already showing the window meerly focus it.
if (configWindow.Visible)
configWindow.Focus();
else
configWindow.ShowDialog();
}
void Exit(object sender, EventArgs e)
{
// We must manually tidy up and remove the icon before we exit.
// Otherwise it will be left behind until the user mouses over.
notifyIcon.Visible = false;
Application.Exit();
}
}
}
And the Program.cs
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace TaskTrayApplication
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Instead of running a form, we run an ApplicationContext.
Application.Run(new TaskTrayApplicationContext());
}
}
}
Threading is hard, concurrency is hard. Background worker and System.Timers are both constructs that run in their own thread.
winforms won't allow for interaction between threads that own a control (read: that created a control) and threads that don't. This is a whole subject apart i wont get into now - theres good stuff to read out there why this is and how to go about it: https://visualstudiomagazine.com/articles/2010/11/18/multithreading-in-winforms.aspx
There are tools to help, one is the dispatchertimer:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatchertimer?view=netcore-3.1
This is a special timer that instead of its own thread, schedules tasks on the main thread. The main thread in a winforms application handles the drawing of controls, showing of the different windows etc. e.g. this 'owns' all controls.
A sample can be seen on msdn, i adopted it here to show you what you could do:
public class TaskTrayApplicationContext : ApplicationContext
{
...
DispatcherTimer dispatcherTimer;
public TaskTrayApplicationContext()
{
...
dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0,0,1);
dispatcherTimer.Start();
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
// Fetch your data via a rest api
var myData = MyDataFunction();
// check and show dialog if the data is not okay
if(myData.Result.Value = 'NOT_OKAY!')
ShowMessage(this, myData.Result); // or something.
}
...
Now since this does not utilize a second thread, this means the main ui thread could be blocked from drawing the windows, reacting to user input etc. because its busy doing work in the timer_tick function. This would for example happen if your rest call takes a long time.
This will make your application freeze and irresponsive. This could be a problem but most likely wont, so lets burn that bridge when we get to it.
I am desperately trying to implement a "Session Inactivity Timeout" for an IT security policy.
sss
However, my inactivity reset logic isn't firing. What am I doing wrong? I can't attach a breakpoint due to Visual Studio crashing on attaching to Silverlight binary. I am wondering if the problem is something very subtle, such as at the time the App constructor is called, there is no "current RootVisual" yet. Tagging with #WPF as well since #Silverlight is dead/obsolescent. Sample code below.
namespace TestApp
{
public partial class App : Application
{
System.Windows.Threading.DispatcherTimer sessionTimer = new System.Windows.Threading.DispatcherTimer();
public App()
{
ResetSessionTimer();
this.RootVisual.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(rootVisual_MouseLeftButtonDown), true);
//this.RootVisual.MouseLeftButtonDown += rootVisual_MouseLeftButtonDown;
InitializeComponent();
}
private void ResetSessionTimer()
{
sessionTimer.Stop();
sessionTimer.Tick -= sessionTimer_Tick;
sessionTimer = new System.Windows.Threading.DispatcherTimer();
sessionTimer.Interval = TimeSpan.FromMinutes(1);
sessionTimer.Tick += sessionTimer_Tick;
sessionTimer.Start();
}
private void sessionTimer_Tick(object sender, EventArgs e)
{
System.Windows.Browser.HtmlPage.Document.GetElementById("LogoutButton").Invoke("click", null);
}
private void rootVisual_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// added this alert to see if code is even firing.
System.Windows.Browser.HtmlPage.Window.Invoke("alert", "hello, world");
ResetSessionTimer();
e.Handled = false;
}
}
}
I figured this out - somewhere in the code, the previous developer was overriding the this.RootVisual element with a new MainPage() in order to enable their homegrown dependency injection / MVVM architecture. I moved the logic from the App() constructor to right after this assignment operator, and it works like magic.
I would like to have a system tray icon appear only when I need to show a balloon tip, then hide the icon when the balloon tip is closed.
However, once the icon is shown, I can't get it to disappear because the event handler is not fired:
public partial class MainWindow : Window {
public static NotifyIcon trayIcon = new NotifyIcon();
public MainWindow() {
InitializeTrayIcon();
}
void InitializeTrayIcon() {
trayIcon.Text = "My App";
trayIcon.Icon = MyApp.Properties.Resources.myIcon;
trayIcon.Visible = false;
//the following never gets fired:
trayIcon.BalloonTipClosed += (sender, e) => {
trayIcon.Visible = false;
};
}
public static void ShowTrayNotification(ToolTipIcon icon, string title, string text, int duration) {
trayIcon.Visible = true;
trayIcon.ShowBalloonTip(duration, title, text, icon);
}
}
The ShowTrayNotification() is called from a method that is triggered by a timer:
public abstract class Watcher {
protected System.Timers.Timer myTimer = new System.Timers.Timer(1000);
//the following is called in a subclass of Watcher, which is instantiated in MainWindow
protected void SetupMyTimer() {
myTimer.AutoReset = true;
myTimer.Elapsed += myTimer_Elapsed;
myTimer.Start();
}
protected virtual void myTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
myTimer.Enabled = false;
MyTimerElapsedCallback();
myTimer.Enabled = true;
}
void MyTimerElapsedCallback() {
MainWindow.ShowTrayNotification(ToolTipIcon.Info, "Hello There!", "Balloon text here.", 5000);
}
}
So the balloon is shown. But BalloonTipClosed in MainWindow is never fired.
I have tried:
putting the (1) creation of the NotifyIcon, (2) displaying of balloon, and (3) setting BalloonTipClosed all in MainWindow, and it works fine (i.e. BalloonTipClosed is fired)
putting (1), (2), and (3) in SetupMyTimer() and it works fine as well
putting (1), (2), and (3) in MyTimerElapsedCallback() and it does not work (i.e. BalloonTipClosed is not fired)
changing BalloonTipClosed to BalloonTipClicked and it does not work as well.
using non-lambda BalloonTipClosed EventHandler, does not work.
with this, I am thinking the problem has to do with the Timer, but I don't know how it's affecting the event handler, nor how to fix.
Any ideas?
You have a threading bug in your code, the timer's Elapsed event is raised on a threadpool thread. You normally get an InvalidOperationException when you do this sort of thing but the check is not implemented for NotifyIcon.
The side-effect of making it visible on the wrong thread is that an otherwise hidden window that is used to receive the event notifications is created on that bad thread. It cannot receive any notifications at all, the threadpool thread does not pump a message loop. Lousy diagnostic, no exception and no good way to see why it goes wrong.
Your ShowTrayNotification() method must use the form's BeginInvoke() method so that the code runs on the UI thread. You make that difficult because the method is static, in an absolute pinch you could use Application.OpenForms[0].BeginInvoke(). But it would certainly be better to have your Watcher class raise an event instead of calling the form's method directly. Or consider using a plain Winforms' Timer, the one you find back in the toolbox. As posted, the Watcher class has no visible added value.
Usually, when you access controls in a Thread you end up with some cross thread exceptions. In my C# WinForms Application I have a picture box and a toolstriplabel which do not cause that exception. I don't understand why, can anybody explain this to me?
Here some code explanation:
In the main form I have a picturebox and a toolstriplabel. Also I have a reference to another Form, which has no controls and no additional source code. And then in the main form there is another object which works with a thread. This thread can raise three different events and the main form is subscribed to these three events.
Event1 causes the toolstriplabel to update (with some information from the thread).
Event2 causes the picturebox to update (with a new picture from the thread).
Event1 and Event2 work perfectly fine. I do not use any invoke methods, I directly change Text and BackgroundImage properties without cross thread exception.
Event3 though makes troubles. It is supposed to show the other form but I receive the cross therad exception. It works only if I use a BeginInvoke to show the form.
Why is that?
Edit:
The multithreading is done by an MJPEGStream object. I subscribe the NewFrame method of that MJPEGStream object.
public partial class Form1 : Form
{
private CAM cam;
private PeekWindow frmPeekWindow;
public Form1()
{
InitializeComponent();
cam = new CAM();
cam.NewImageMessageEvent += new NewImageEventHandler(cam_NewImageMessageEvent);
cam.DetectionEvent += new DetectionEventHandler(cam_DetectionEvent);
cam.FpsChangedMessageEvent += new FpsChangedEventHandler(cam_FpsChangedMessageEvent);
cam.DetectionThreshold = (float)this.numDetectionThreshold.Value;
frmPeekWindow = new PeekWindow();
// without the next two lines, frmPeekwindow.Show() won't work if called in an event
frmPeekWindow.Show();
frmPeekWindow.Hide();
}
void cam_FpsChangedMessageEvent(object sender, FpsChangedEventArgs e)
{
lblFPS.Text = string.Format("fps: {0:0.0}", e.FPS);
}
void cam_DetectionEvent(object sender, DetectionEventArgs e)
{
if (chkEnablePeakWindow.Checked)
{
if (frmPeekWindow.InvokeRequired)
{
frmPeekWindow.Invoke((MethodInvoker)delegate()
{
frmPeekWindow.Show();
frmPeekWindow.setImage(e.Image);
});
}
else
{
frmPeekWindow.Show();
frmPeekWindow.setImage(e.Image);
}
}
}
void cam_NewImageMessageEvent(object sender, NewImageEventArgs e)
{
picStream.BackgroundImage = e.Image;
}
}
And here's the CAM class:
class CAM
{
private object lockScale = new object();
private MJPEGStream stream;
private Bitmap image;
public event NewImageEventHandler NewImageMessageEvent;
public event FpsChangedEventHandler FpsChangedMessageEvent;
public event DetectionEventHandler DetectionEvent;
// configure (login, pwd, source)
public CAM()
{
this.stream = new MJPEGStream("...");
this.stream.Login = "...";
this.stream.Password = "...";
this.stream.NewFrame += new NewFrameEventHandler(OnNewFrame)
}
private void OnNewFrame(object sender, NewFrameEventArgs ev)
{
try
{
FpsChangedMessageEvent(this, new FpsChangedEventArgs(10));
// get image
image = ev.Frame;
NewImageMessageEvent(this, new NewImageEventArgs(new Bitmap(image)));
DetectionEvent(this, new DetectionEventArgs(new Bitmap(image)));
}
catch (Exception ex)
{
Console.Out.WriteLine(ex.Message);
}
}
}
You won't get cross thread exception, but it doesn't mean that this is a safe operation. There is always a possibility for your control to go unstable. You just don't know when it will happen.
See the following explanation from Microsoft.
http://msdn.microsoft.com/en-us/library/ms171728.aspx
Access to Windows Forms controls is not inherently thread safe. If you
have two or more threads manipulating the state of a control, it is
possible to force the control into an inconsistent state. Other
thread-related bugs are possible, such as race conditions and
deadlocks. It is important to make sure that access to your controls
is performed in a thread-safe way.
I have these three possibilites in mind:
The action is already dispatched to the gui thread.
The action doesn't need to be dispatched currently.
The action is somehow executed from the gui thread.
It's most likely number 3.
You don't necessarily always have to call BeginInvoke/Invoke. Sometimes the operation is running on the foreground thread, sometimes it is in the background.
Per the microsoft samples that are everywhere, You can SHOULD check to see if calling BeginInvoke/Invoke is required.
private void SetTextStandardPattern()
{
if (this.InvokeRequired)
{
this.Invoke(SetTextStandardPattern);
return;
}
this.text = "New Text";
}
Here is a nice microsoft article that has a sample:
http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
and here is another article on how to "avoid" the pattern:
http://www.codeproject.com/Articles/37642/Avoiding-InvokeRequired
I'm implementing a visual version of Tracert (as a learning exercise) in WPF where results go to a listbox. The issues are (1) the listbox bound to tracertDataView is not updating, but (2) my entire application hangs.
I'm sure #2 is a threading issue but I'm not sure how to correct it (in the right way). In addition I'm not sure my technique of updating / binding the results of "DoTrace" are correct.
Here is my datasource in App.xaml
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=TracertResultNodes}"
x:Key="tracertDataView" />
</Window.Resources>
App.xaml.cs
public partial class App : Application
{
private ObservableCollection<TracertNode> tracertResultNodes = new ObservableCollection<TracertNode>();
public void AppStartup(object sender, StartupEventArgs e)
{
// NOTE: Load sample data does work correctly.. and displays on the screen.
// subsequent updates do not display
LoadSampleData();
}
private void LoadSampleData()
{
TracertResultNodes = new ObservableCollection<TracertNode>();
TracertNode t = new TracertNode();
t.Address = new System.Net.IPAddress(0x2414188f);
t.RoundTripTime = 30;
t.Status = System.Net.NetworkInformation.IPStatus.BadRoute;
TracertResultNodes.Add(t);
}
public ObservableCollection<TracertNode> TracertResultNodes
{
get { return this.tracertResultNodes; }
set { this.tracertResultNodes = value; }
}
}
Here is the MainWindow code
public partial class MainWindow : Window
{
CollectionViewSource tracertDataView;
TraceWrapper _tracertWrapper = null;
public MainWindow()
{
InitializeComponent();
_tracertWrapper = new TraceWrapper();
tracertDataView = (CollectionViewSource)(this.Resources["tracertDataView"]);
}
private void DoTrace_Click(object sender, RoutedEventArgs e)
{
((App)Application.Current).TracertResultNodes = _tracertWrapper.Results;
_tracertWrapper.DoTrace("8.8.8.8", 30, 50);
}
}
FYI Internal implementation Detail of instance object "traceWrapper.DoTrace"
/// <summary>
/// Trace a host. Note that this object internally calls the Async implementation of .NET's PING.
// It works perfectly fine in a CMD host, but not in WPF
/// </summary>
public ObservableCollection<TracertNode> DoTrace(string HostOrIP, int maxHops, int TimeOut)
{
tracert = new Tracert();
// The following is triggered for every host that is found, or upon timeout
// (up to 30 times by default)
AutoResetEvent wait = new AutoResetEvent(false);
tracert.waiter = wait;
tracert.HostNameOrAddress = HostOrIP;
tracert.Trace();
this.Results = tracert.NodeList;
while (tracert.IsDone == false)
{
wait.WaitOne();
IsDone = tracert.IsDone;
}
return tracert.NodeList;
}
I don't understand how u used AutoResetEvent, i guess it is not supposed to be used in this way :)
But since Trace run already in another thread, are you sure there is not an event "OnTracertComplete" or something like that in your Tracert class?
If there is not, why you just don't put a DispatchTimer into your application?
That timer would periodically poll until tracert.IsDone becomes true.
If you block the execution of the application thread until an operation completes, you block the execution of the window event loop so window will never be updated.
Another important thing: you cannot update ObservableCollections from another thread.
Be careful and be sure that everything that is updated in the WPF window is executed from the same thread of the window. Don't know what your Trace class do exactly, but your problem here seems to be of course the wait loop, that don't makes sense in a GUI application.
Use notification events or a timer to poll the result. A timer with 1 second resolution seems good to me for this particular implementation and the performance inpact is absolutely minimal.
This is a possible implementation if you are able to modify the Tracert class.
public delegate void TracertCallbacHandler(Tracert sender, TracertNode newNode);
public class Tracert
{
public event TracertCallbacHandler NewNodeFound;
public event EventHandler TracertCompleted;
public void Trace()
{
....
}
// This function gets called in tracert thread\async method.
private void FunctionCalledInThreadWhenPingCompletes(TracertNode newNode)
{
var handler = this.NewNodeFound;
if (handler != null)
handler(this, newNode);
}
// This function gets called in tracert thread\async methods when everything ends.
private void FunctionCalledWhenEverythingDone()
{
var handler = this.TracertCompleted;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
And here is the code to run the tracert,
This is TracertWrapper.
// Keep the observable collection as a field.
private ObservableCollection<TracertNode> pTracertNodes;
// Keep the instance of the running tracert as a field, we need it.
private Tracert pTracert;
public bool IsTracertRunning
{
get { return this.pTracert != null; }
}
public ObservableCollection<TracertNode> DoTrace(string hostOrIP, int maxHops, int timeOut)
{
// If we are not already running a tracert...
if (this.pTracert == null)
{
// Clear or creates the list of tracert nodes.
if (this.pTracertNodes == null)
this.pTracertNodes = new ObservableCollection<TracertNode>();
else
this.pTracertNodes.Clear();
var tracert = new Tracert();
tracert.HostNameOrAddress = hostOrIP;
tracert.MaxHops = maxHops;
tracert.TimeOut = timeOut;
tracert.NewNodeFound += delegate(Tracert sender, TracertNode newNode)
{
// This method is called inside Tracert thread.
// We need to use synchronization context to execute this method in our main window thread.
SynchronizationContext.Current.Post(delegate(object state)
{
// This method is called inside window thread.
this.OnTracertNodeFound(this.pTracertNodes, newNode);
}, null);
};
tracert.TracertCompleted += delegate(object sender, EventArgs e)
{
// This method is called inside Tracert thread.
// We need to use synchronization context to execute this method in our main window thread.
SynchronizationContext.Current.Post(delegate(object state)
{
// This method is called inside window thread.
this.OnTracertCompleted();
}, null);
};
tracert.Trace();
this.pTracert = tracert;
}
return this.pTracertNodes;
}
protected virtual void OnTracertCompleted()
{
// Remove tracert object,
// we need this to let the garbage collector being able to release that objects.
// We need also to allow another traceroute since the previous one completed.
this.pTracert = null;
System.Windows.MessageBox.Show("TraceRoute completed!");
}
protected virtual void OnTracertNodeFound(ObservableCollection<TracertNode> collection, TracertNode newNode)
{
// Add our tracert node.
collection.Add(newNode);
}
The issue is that not only is the listbox not updating, but my entire application hangs.
This is probably due to the AutoResetEvent blocking in DoTrace. You explicitly call Wait.WaitOne(); on the event handle, but as far as I can tell, never Set() it. This will cause the application to hang forever as soon as you call Wait.WaitOne().
It sounds like tracert.Trace() is an asynchronous method. Does it include some form of callback/event to notify you upon completion? If so, you should use that, not poll in a loop, to determine when it's complete.
(1) the listbox bound to tracertDataView is not updating
You won't see the updates to your listbox, as you're assigning a new collection to the TracertResultNodes property, the binding in this case simply does not work, because a new collection was assigned.
In addition to ensuring that the collection is updated in the same thread as outlined by Salvatore below, you should only add or remove items from the existing collection, and NOT assign the new one generated by your DoTrace function.
private void DoTrace_Click(object sender, RoutedEventArgs e)
{
foreach(var traceNode in _tracertWrapper.Results)
{
((App)Application.Current).TracertResultNodes.Add(traceNode);
}
_tracertWrapper.DoTrace("8.8.8.8", 30, 50);
}
If you do assign a new one, then you'd need to implement INotifyPropertyChanged on your App class, am not sure how (or whether) that would work though (I have not tried this before).