我正在WPF中实现Tracert的可视化版本(作为一个学习练习),其中的结果会转到一个列表框中。问题是:(1)绑定到tracertDataView的列表框没有更新,但是(2)我的整个应用程序挂起。
我确信#2是一个线程问题,但我不知道如何纠正它(以正确的方式)。此外,我不确定我的更新/绑定"DoTrace“结果的技术是否正确。
这是我在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; }
}
}下面是MainWindow代码
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);
}
}实例对象"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;
}发布于 2011-09-01 16:24:30
我不明白你是如何使用AutoResetEvent的,我想它不应该以这样的方式使用:)
但是由于跟踪已经在另一个线程中运行,您确定在Tracert类中没有事件"OnTracertComplete“或类似的事件吗?
如果没有,为什么不将DispatchTimer放在应用程序中呢?这个计时器会定期轮询,直到tracert.IsDone变为真为止。如果在操作完成之前阻止应用程序线程的执行,则会阻止窗口事件循环的执行,因此将永远不会更新窗口。
另一件重要的事情是:不能从另一个线程更新ObservableCollections。小心,并确保在WPF窗口中更新的所有内容都是从窗口的同一个线程执行的。不知道Trace类到底做了什么,但是这里的问题似乎是等待循环,这在GUI应用程序中没有意义。
使用通知事件或计时器轮询结果。对于这个特定的实现来说,具有1秒分辨率的计时器对我来说似乎很好,而性能inpact绝对是最小的。
如果您能够修改Tracert类,这是一种可能的实现。
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);
}
}这是运行跟踪的代码,这是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);
}发布于 2011-09-01 16:23:57
问题不仅是列表框没有更新,而且我的整个应用程序都挂起了。
这可能是由于AutoResetEvent阻塞在DoTrace。您在事件句柄上显式地调用了Wait.WaitOne();,但据我所知,永远不要对它进行Set()。这将导致应用程序在调用Wait.WaitOne()时永久挂起。
听起来tracert.Trace()是一种异步方法。它是否包括某种形式的回调/事件,以便在完成时通知您?如果是这样的话,您应该使用它,而不是在循环中轮询,以确定它何时完成。
发布于 2011-09-01 16:38:36
(1)绑定到tracertDataView的列表框没有更新
您将不会看到对列表框的更新,因为您要将一个新集合分配给TracertResultNodes属性,在本例中,绑定根本无法工作,因为分配了一个新集合。
除了确保集合在下面由萨尔瓦多概述的相同线程中更新之外,您还应该只从现有集合中添加或删除项,而不是分配由您的DoTrace函数生成的新项。
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);
}如果您确实分配了一个新的应用程序,那么您需要在应用程序类上实现INotifyPropertyChanged,不确定它是如何工作的(或者是否),尽管如此(我以前还没有尝试过)。
https://stackoverflow.com/questions/7273226
复制相似问题