2

結果がリストボックスに送られる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);
    }
}

FYI内部実装インスタンスオブジェクト「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;
    }
4

3 に答える 3

2

AutoResetEvent をどのように使用したかわかりません。この方法で使用することは想定されていないと思います:)

しかし、Trace は既に別のスレッドで実行されているため、Tracert クラスに "OnTracertComplete" などのイベントが存在しないのでしょうか?

そうでない場合、DispatchTimer をアプリケーションに入れないのはなぜですか? そのタイマーは、tracert.IsDone が true になるまで定期的にポーリングします。操作が完了するまでアプリケーション スレッドの実行をブロックすると、ウィンドウ イベント ループの実行がブロックされ、ウィンドウが更新されなくなります。

もう 1 つの重要なこと: ObservableCollections を別のスレッドから更新することはできません。WPF ウィンドウで更新されるものはすべて、ウィンドウの同じスレッドから実行されるように注意してください。あなたの Trace クラスが正確に何をするのかわかりませんが、ここでの問題はもちろん待機ループのようで、GUI アプリケーションでは意味がありません。

通知イベントまたはタイマーを使用して結果をポーリングします。この特定の実装では、分解能が 1 秒のタイマーが適しているように思われ、パフォーマンスへの影響は最小限に抑えられます。

これは、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);
        }

    }

そして、これが tracert を実行するコードです。これは 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-01T16:24:30.520 に答える
1

(1) tracertDataView にバインドされたリストボックスが更新されない

新しいコレクションを TracertResultNodes プロパティに割り当てているため、リストボックスへの更新は表示されません。新しいコレクションが割り当てられたため、この場合のバインディングは単に機能しません。

以下の Salvatore によって概説されているように、コレクションが同じスレッドで更新されるようにすることに加えて、既存のコレクションからアイテムを追加または削除するだけで、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);
    }

新しいものを割り当てる場合は、 App クラスに INotifyPropertyChanged を実装する必要がありますが、それがどのように機能するか (または機能するかどうか) はわかりません (私はこれを試したことはありません)。

于 2011-09-01T16:38:36.030 に答える
1

問題は、リストボックスが更新されないだけでなく、アプリケーション全体がハングすることです。

これはおそらく でのAutoResetEventブロックが原因DoTraceです。イベントハンドルを明示的に呼び出しますWait.WaitOne();が、私が知る限り、それは決してありませんSet()。これにより、 を呼び出すとすぐにアプリケーションが永久にハングアップしますWait.WaitOne()

tracert.Trace()非同期メソッドのようです。完了時に通知する何らかの形式のコールバック/イベントが含まれていますか? その場合は、ループでポーリングするのではなく、それを使用して、いつ完了したかを判断する必要があります。

于 2011-09-01T16:23:57.477 に答える