2016-04-04 10 views
0

Ich habe ein sehr seltsames Problem. Der Hintergrund ist, dass wir eine Zuordnung zwischen Word ContentControl und einem benutzerdefinierten Objekt haben, das wir verwenden, um einige Information zu speichern, die sich auf den Inhalt in dem Steuerelement bezieht. Wir verwenden eine SortedList<ContentControl, OurCustomObject>, um diese Zuordnung aufrechtzuerhalten. Der SortedList-Teil ist nützlich, um das nächste/vorherige Inhaltssteuerelement zu finden und um schnell auf das Objekt zugreifen zu können, das einem Inhaltssteuerelement zugeordnet ist.Zugriff auf Range.Start in Loop verbessert die Leistung von Comparer

Um dies einzurichten, tun wir etwas wie folgt aus:

var dictOfObjs = Globals.ThisAddIn.Application.ActiveDocument.ContentControls 
    .Cast<ContentControl>() 
    .ToDictionary(key => key, elem => new OurCustomObject(elem)); 
var comparer = Comparer<ContentControl> 
    .Create((x, y) => x.Range.Start.CompareTo(y.Range.Start)); 
var list = new SortedList<ContentControl, OurCustomObject>(dictOfObjs, storedcomparer); 

Das schien ziemlich gut zu funktionieren, aber ich es vor kurzem versucht, auf einem Dokument mit ~ 5000 Inhalt Kontrollen, und es verlangsamt auf ein absolutes crawl (3+ Minuten, um die SortedList zu instanziieren).

Also das ist seltsam genug, aber noch mehr Fremdheit sollte noch kommen. Ich fügte einige Protokollierung hinzu, um herauszufinden, was vor sich ging, und fand, dass das Protokollieren des Anfangs jedes ContentControl vor der Verwendung in der Liste es um einen Faktor von ~ 60 beschleunigte. (Ja, das Hinzufügen von Logging beschleunigte es!). Hier ist der viel schnelleren Code:

var dictOfObjs = Globals.ThisAddIn.Application.ActiveDocument.ContentControls 
    .Cast<ContentControl>() 
    .ToDictionary(key => key, elem => new OurCustomObject(elem)); 

foreach (var pair in dictOfObjs) 
{ 
    _logger.Debug("Start: " + pair.Key.Range.Start); 
} 

var comparer = Comparer<ContentControl> 
    .Create((x, y) => x.Range.Start.CompareTo(y.Range.Start)); 
var list = new SortedList<ContentControl, OurCustomObject>(dictOfObjs, storedcomparer); 

Der Konstruktor für SortedList ruft Array.Sort<TKey, TValue>(keys, values, comparer); auf den Schlüssel und Werte des Wörterbuchs. Ich kann nicht herausfinden, warum der Zugriff auf die Range-Objekte in einer Schleife vorher beschleunigen würde. Vielleicht etwas mit der Reihenfolge zu tun, in der sie zugegriffen werden? Die foreach Schleife greift auf sie in der Reihenfolge zu, in der sie im Dokument erscheinen, während Array.Sort überall herum springt.

Edit: Wenn ich SortedList sage, meine ich System.Collections.Generic.SortedList<TKey, TValue>. Hier ist der Code für den Konstruktor Ich verwende:

public SortedList(IDictionary<TKey, TValue> dictionary, IComparer<TKey> comparer) 
    : this((dictionary != null ? dictionary.Count : 0), comparer) { 
    if (dictionary==null) 
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary); 

    dictionary.Keys.CopyTo(keys, 0); 
    dictionary.Values.CopyTo(values, 0); 
    Array.Sort<TKey, TValue>(keys, values, comparer); 
    _size = dictionary.Count;    
} 
+0

Konnten Sie den SortedList-Konstruktorcode zur Verfügung stellen? Wenn das ist, wo die Verlangsamung stattfindet, müssen wir das sehen ... Ein bekanntes Problem in Word, fwiw, ist, dass es oft schneller ist, die Schleife für + index statt foreach zu verwenden.Ich denke, dass es etwas damit zu tun hat, wie Word "verfolgen" muss, wo sich die Objekte befinden, anstatt es einfach mit dem Index aufnehmen zu können. –

+0

Sicher - ich habe den Code zu meiner Frage hinzugefügt. Die Zeile, die so lange dauert, ist diejenige, die ich bereits erwähnt habe, den Aufruf von 'Array.Sort'. Mein Verständnis ist, dass 'Array.Sort' den Vergleich verwendet, um die CCs basierend auf ihrem Bereichsstart zu sortieren. Ich verstehe einfach nicht, warum der Zugriff auf 'Range.Start' plötzlich so langsam wird, sobald eine ausreichende Anzahl von CCs im Dokument vorhanden ist, und kann dennoch beschleunigt werden, indem man sie vorher iteriert. Sehr verwirrend für mein armes Gehirn! – Zout

Antwort

0

Neben Ihrer Performance-Problem, denke ich Ihre Lösung (es sei denn, das Dokument in der Tat ein statisches Dokument) wird im Laufe der Zeit nicht. Range.Start-Standorte neigen dazu, sich abhängig vom Hinzufügen/Entfernen von Inhalten aus Ihrem Dokument zu verschieben.

Um dies zu beweisen, fügen Sie das folgende kleine Makro und führen Sie es aus VBA:

Sub testccstart() 

    Dim cc As ContentControl 
    Set cc = ActiveDocument.ContentControls.Add(wdContentControlRichText) 

    MsgBox cc.Range.Start 

    ActiveDocument.Range(0).InsertBefore "Blablabla" 

    MsgBox cc.Range.Start 

End Sub 

Sie finden Range.Start bemerken von der Content Control von 1 bis 10 die Minute verschoben Sie den Text eingegeben an der Beginn des Dokuments. Daher müssen Sie bei allen Änderungen in Ihrem Dokument die auf Range.Start basierenden Listen neu laden.

Die Antwort auf Ihre Frage ist, dass durch das Hinzufügen der Protokollierung Sie einige ausgelöst haben, was sie 'Lazy Loading' nennen (Laden nur Dinge, wenn tatsächlich zugegriffen wird). Es gibt alle Arten von Optimierungen innerhalb von Office, die nicht immer klar sind (z. B. ist der Zugriff auf Excel-Bereiche mit Arrays oft viel schneller).

testete ich ein bisschen und frage mich, ob dies eine Lösung für Sie sein:

var dictOfObjs = document.ContentControls 
         .Cast<ContentControl>() 
         .ToDictionary(key => key.Range.Start, elem => new OurCustomObject(elem)); 

var comparer = Comparer<int>.Create((x, y) => x.CompareTo(y)); 

var list = new SortedList<int, OurCustomObject>(dictOfObjs, comparer); 

Anstatt die Content Control als Schlüssel zu verwenden (Ich denke, man die Content Control speichern bereits in OurCustomObject?) Unter der Annahme der Inhalt Steuerelemente haben jeweils eine eindeutige Startposition Verwenden Sie die Startposition als Schlüssel, dies brachte meine Verarbeitung von 1600 Elementen von 48 auf 5 Sekunden zurück ...

+0

Ich machte mir auch Sorgen darüber (dass die Reihenfolge beim Ändern des Dokumentinhalts geändert werden würde) - aber es ist eigentlich kein Problem, da das Hinzufügen oder Entfernen von Text zu einem Zeitpunkt den Bereichsstart aller folgenden Inhaltssteuerelemente nach vorne oder nach hinten verschiebt , beziehungsweise. – Zout

+0

Stimmt, aber wird nicht sein, wenn jemand - den Text - bewegt. Außerdem glaube ich, dass Sie die Inhaltssteuerelemente in den Kopf- und Fußzeilen des Dokuments und/oder Inhaltssteuerelemente in Shapes vermissen. Lesen Sie hier darüber nach: http://StackOverflow.com/Questions/4605179/How-to-get-the-list-of-all-content-controls-in-the-document Seien Sie sehr vorsichtig mit Content-Control-Objekten, Dinge können Gehen Sie knifflig, wenn nicht im Detail getestet ;-) –

+0

Die Lösung für das Verschieben war die Verwendung der ContentControlBeforeDelete ContentControlAfterAdd-Ereignisse (die aufgerufen werden, wenn das Inhaltssteuerelement verschoben wird), um sicherzustellen, dass SortedList geordnet bleibt. Ich wusste nicht, dass Sie CCs zu Formen hinzufügen können - interessant! Ich denke jedoch nicht, dass dies ein Problem ist - der Inhalt, der von den CCs verpackt wird, ist etwas standardisiert und ich glaube, dass er immer im normalen Teil des Dokuments sein wird. Über die Lazy Loading, haben Sie eine Ahnung, ob es eine Möglichkeit gibt, es zu erzwingen? Die Protokollierung "Workaround" scheint schrecklich hacky für mich. Danke für deine Zeit, übrigens. – Zout

Verwandte Themen