2016-10-27 54 views
0

我有一個HID設備,我在200hz-600hz左右進行通信,並將數據解釋爲表示HID設備屬性的類對象。這個類在其屬性上實現了INotifyPropertyChanged,並且由於通信速度的原因,我認爲處理隊列正在陷入困境,因爲控件在幾分鐘後似乎變得遲鈍而「框架」。當處理大量數據時INotifyPropertyChanged崩潰

在.net中是否有方法可以幫助解決這個問題,或許是事件處理程序池或某種類型的隊列?

遺憾的是沒有我的HID設備我不知道我的代碼沒有任何用處任何人複製的,但生病包括一對夫婦的相關片段只是爲了顯示我的實現:

public enum DataEvents { onNone = 0, onStatus = 1, onInput = 2, onOutput = 4, onReport = 8}; 
public class Controller: INotifyPropertyChanged, IDisposable, INotifyDisposed 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    public event EventHandler Disposing; 
    public event EventHandler Disposed; 
    public event EventHandler ReportReceived; 

    internal void callPropertyChanged(string PropertyName) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName)); 
    } 

    internal void callReportReceived() 
    { 
     ReportReceived?.Invoke(this, EventArgs.Empty); 
    } 

    public bool Touch1 
    { 
     get { return _Touch1; } 
     private set { if (value != _Touch1) { _Touch1 = value; if (RaiseUpdateEvents.HasFlag(DataEvents.onInput)) callPropertyChanged("Touch1"); } } 
    } 
    private bool _Touch1 = false; 

    //There are many more properties but all of them follow this pattern, and have several different types 
} 

我的對象是從填充一個System.Threading.Thread在循環輪詢HID設備的報告,HID設備讀取方法是一個阻塞呼叫,因此該循環不是死循環,並且被限制爲設備的數據速率,通常聲明爲200hz-600hz。

編輯:值得注意的是,我特別感興趣的WPF綁定。

+0

如果這是Wpf,則會導致分派器上的積壓。不太經常地舉辦活動 – Gusdor

+0

@Gusdor當HID設備屬性發生變化時,會引發該事件,那麼我將如何緩解該問題 – Wobbles

+0

@Gusdor爲每個屬性考慮排隊,如果爲屬性引發多個事件,它將清除所有最近,但我不能看到如何不會增加滯後和延遲本身,特別是在這些速度。 – Wobbles

回答

1

讓我們做一些簡單的事。無論如何,你需要下載樣本你的數據。在200hz-600hz的UI上顯示數據通常會導致問題。

我的建議 - 以您可以使用的最長持續時間啓動您選擇的計時器。讓我們從1000ms開始。

  1. 每個計時器到期時,觸發該事件關閉所有 屬性更改事件
  2. 不要擡起屬性更改事件時,該設備的更新
每秒一次

,你的處理將更新和您的應用程序將保持響應。

+0

對於硬件交互,即使是100ms計時器也是不可接受的。我認爲我的關鍵是某種隊列,如果一個propery的事件開始疊加起來,它只會取最近的值。 – Wobbles

+1

@Wobbles 100ms應該是絕對好的。 20ms也應該工作得很好。重要的是你沒有儘可能快地運行,因爲這總會在某個地方造成瓶頸。我給出了1秒的例子,因爲它可以在用戶界面和任何你可能輸出的調試日誌中看到。 – Gusdor

+2

該規模的「硬件交互」不應該耦合到GUI。 –

6

在處理WPF中的近實時系統時(我花了6年時間研究它們),您有幾個選項。首先,我將列出幾點想法:

  • 要獲取所有WPF綁定通過一個事件更新,請使用string.Empty作爲您的屬性名稱。
  • 您的問題可能不完全是由於事件。 WPF有很多影響內存管理的問題。

所以你要問的問題是用戶多久需要看到任何一種改變?人類持久的視力是1/10秒,即100ms。任何比這更頻繁的更新都會被浪費,但更多的時候是這樣,即使那樣頻繁。

每秒一個事件?

在我的情景中,我們確定我們只需要每秒更新一次屏幕上的所有內容。儘管我們每秒接收數據達到12次(每個樣本83毫秒),但我們收集並平均數據以平滑數據。它爲我們的用戶提供了更好的意識。

  • 我們構建了我們的視圖模型,每秒用一個主定時器調用Update()方法。
  • 該機型實現INotifyPropertyChanged避免binding memory leak,但只提出了一個屬性更改事件與string.Empty造成UI刷新

最小化的對象創建

垃圾收集所度過的每一毫秒的用戶無法與應用程序交互的時間顯着。每次提出事件時,都必須創建要發送的事件對象。雖然技術上可以創建一次事件對象並引發同一個實例,但WPF爲您創建對象實例的地方有幾個。以下是您需要注意的事項:

  • DataTemplate本質上爲您想要模板的每個對象創建一個新模板。儘可能嘗試使用虛擬化,並儘量減少對此的使用。
  • ResourceDictionary每次在控件中聲明資源字典時,都會創建一個新實例。在App.xaml中包含所有合併的資源字典比保留包含不同用戶控件中的相同字典更好。特別是如果你有用戶控制在DataTemplate
  • ContentPresenter不是你的朋友。

爲了解釋遠一點,ContentPresenter將你的對象,查找它的類型控件的ResourceDictionary找到DataTemplate實例爲您的數據。當您需要將窗口的特定部分替換爲另一個控件時,它可能很方便,但它確實花費很大。儘量減少使用。

保持硬件/通訊在後臺

我們成立了專門的線程來處理通信和處理的需求。這可以讓用戶界面保持響應,同時我們可以對數據進行一些DSP /統計減少。

使用你需要靠近顯示屏上實時更新一個內存分析器

任何時候,你必須要特別小心內存使用。這是我們不得不面對的頭號問題。

  • 您的應用程序開始時確定,但它開始降級
  • 一兩分鐘後,確保你沒有持有反對,你不要指望
  • 查找倖存垃圾對象實例收集事件
-1

在這裏討論了許多很好的信息,但就實施,這是我去的方式:

private Thread PropertyChangedQueueThread; 
    private List<string> ChangedPropertiesQueue = new List<string>(); 
    private ManualResetEvent PropertyChangedQueueBlocker = new ManualResetEvent(false); 

    private void PropertyChangedQueueWorker() 
    { 
     while (!this.disposingValue) 
     { 
      PropertyChangedQueueBlocker.WaitOne(); 

      string PropName = ChangedPropertiesQueue.Last(); 
      ChangedPropertiesQueue.RemoveAll(i => i == PropName); 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropName)); 

      if (ChangedPropertiesQueue.Count() == 0) 
       PropertyChangedQueueBlocker.Reset(); 
     } 
    } 

    internal void QueuePropertyChangedEvent(string PropertyName) 
    { 
     ChangedPropertiesQueue.Add(PropertyName); 
     PropertyChangedQueueBlocker.Set(); 
    } 

似乎工作得很好,它看起來可以自我擴展,所以無論有多少屬性更改進入,只有最近一次纔會顯示給UI。除非我想象性能提高,否則它對我的需求似乎運作良好。

我不太清楚我使用List和.RemoveAll會導致哪種性能損失,因爲每次調用RemoveAll都會按照我的理解重新索引列表。也許某種類型的索引可以是一個字符串的密鑰更適合,如字典

+0

只需發送'string.Empty'作爲屬性名稱即可降低複雜性。這樣可以避免由於屬性名稱被掩埋和丟棄而導致UI部分的潛在陷阱不更新。 –

+0

@BerinLoritsch你是指在最後的事件中調用? – Wobbles

+0

@BerinLoritsch當這樣做的時候,表現會變得很糟糕,懷疑有太多的屬性,並且並不是所有的屬性都會不斷變化,以獲得任何性能價值。 – Wobbles