יום שני, 7 במרץ 2011

פתרון אפשרי לבעיית ביצועים ב TreeView

בעיית performance חמורה כשניסיתי לעשות custom tree view + data binding להרבה נתונים.

בסופו של דבר התברר לי שה virtualization של ה TreeView נדפק כאשר מורידים trigger שמגיע ב default template של TreeViewItem

מה שגרם לייצור של כל ה TreeViewItems בפעם הראשונה בבת אחת.

התיקון הוא ב style של TreeViewItem (או מה שיורש ממנו) שמתחיל ב:

<Style TargetType="{x:Type TreeViewItem}">

באזור ה triggers

<ControlTemplate.Triggers>

חייבים להשאיר את השורות הבאות (או multi trigger וכו' שדואג שזאת תהיה התוצאה):

<Trigger Property="IsExpanded" Value="false">
    <Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
</Trigger>



במצב הזה העץ לא ייצור את הילדים של הענפים בעץ עד שיפתחו אותם ע"י Expand

יום חמישי, 24 בפברואר 2011

Simple exntesnion for Task exception handling

כשעובדים עם Task ההמלצה של MSDN היא לתפוס את ה Exceptions ע"י Wait בתוך try-catch
מה שגורם לסרבול של הקוד , ובנוסף תוקע את ה task במקום לעבוד אסינכרוני.
כתבתי extension method שמאפשר לעשות logging ולטפל ב exception במקום אחד מרכזי.
ע"י שימוש ב HandleExceptionAsync
Task.Factory.StartNew(() =>
{
throw new Exception("test exception");
}).HandleExceptionAsync();


הדוגמא שלי גם רושמת ל logger שהוא חלק מהפרויקט שלי.

בנוסף ניתן להירשם ל event שיעלה בכל פעם שיש exception:
TaskHandlerExtensions.TaskExceptionRaised += t =>
{
   //process here the exception... 
   //t.Exception
};
Task.Factory.StartNew(() =>
{
throw new Exception("test exception");
}).HandleExceptionAsync();

את הקוד המלא ניתן להוריד מכאן (העליתי את כל הקוד כי אני עובד עם תשתית logging )  הקובץ הספציפי נמצא ב:

Diagnostics\Roniz.Diagnostics.Logging\TaskHandlerExtensions.cs

או ניתן להורדה מכאן רק את הקובץ הספציפי ולשנות אם יש צורך את ה dependency ל ILog

יום ראשון, 13 בפברואר 2011

Scalable state synchronization using P2P - part 3 – איך להשתמש בתשתית באפליקציה שלך

בפעם הקודמת הסברתי על היכולות של התשתית ואיך להריץ את האפליקציה לדוגמא , בפוסט הזה אני אסביר איך כל אחד יכול לעבוד איתה בפרוייקט שלו.

 

אם לא מעוניינים ב NuGet אפשר ישר לקפוץ לשלב הבא "הוספה ידנית – ללא NuGet"

התקנה באמצעות NuGet

אני מתבסס על זה שכבר יודעים מה זה NuGet , אם בכל זאת לא מכירים אפשר לקרוא כאן ובעברית

Add Library Package reference:
Add library package reference

חיפוש והתקנה של החבילה P2PSync
Add library package reference dialog
Add library package reference dialog - licence

לאחר שהחבילה הותקנה נוספים לפרויקט מספר assemblies וכמו כן קובץ app.config מתעדכן (או נוצר אם לא היה קודם)
P2PSync - package installed

ה assemblies:
(dependencies for NLog)
NLog.dll
NLog.Extended.dll – הקובץ הזה איננו חובה וניתן להוריד אותו , במיוחד באפליקציות עם .Net client profile – שאז הוא לא מתאים.
Roniz.Diagnostics.Logging.dll - מעטפת שלי ל NLog
כל ה assemblies הללו כדי לתמוך ב Logging , אני משתמש ב Log2Console , ניתן להוריד מ codeplex.
וה assemblies העיקריים של התשתית:
Roniz.WCF.P2P.Channels.dll
Roniz.WCF.P2P.Messages.dll
Roniz.WCF.P2P.Sync.dll
Roniz.Diagnostics.Logging.dll

הוספה ידנית – ללא NuGet

במידה ולא רוצים להשתמש ב NuGet צריך להוריד את הגרסה האחרונה מ codeplex.
ולהוסיף את הקבצים הבאים לפרויקט:

Roniz.Diagnostics.Logging.dll
Roniz.WCF.P2P.Channels.dll
Roniz.WCF.P2P.Messages.dll

בנוסף להוריד מ NLog ולהוסיף את הקבצים הבאים:
NLog.dll

מימוש ה business logic הספציפי של הפרויקט

המימוש נעשה ע"י ירושה מRoniz.WCF.P2P.Sync.Interfaces.ISynchronizationBusinessLogic - באפליקציה לדוגמא זה ה Roniz.WCF.P2P.ApplicationTester.MySynchronizationBusinessLogic

מימוש הודעות בין ה peers:

FullPresenceInfo מכיל את הנתונים שישלחו מה peer לשאר ה peers כשהסטטוס שלו נהפך ל Online , באפליקציה לדוגמא זה ה Roniz.WCF.P2P.ApplicationTester.Messages.MyFullPresenceInfo class.
CompactPresenceInfo - מכיל את הנתונים שישלחו מה peer לשאר ה peers כשקוראים ל close ב manager – זה המקום שה peers האחרים יכולים לדעת ש peer מסוים ירד ולפעול בהתאם , באפליקציה לדוגמא Roniz.WCF.P2P.ApplicationTester.Messages.MyCompactPresenceInfo.
BusinessLogicMessageBase – ה base class שמשמש את כל ההודעות ה business logics האחרות (למשל הודעות עדכונים).

שימוש בקוד (הקוד מהאפליקציה לדוגמא):
אתחול synchronization manager:
כשמאתחלים את SynchronizationStateManager צריך לספק לו את הclass שמממש את ה businesslogic:
SyncManager = new SynchronizationStateManager(MySynchronizationBusinessLogic);

וכשרוצים להתחיל לסנכרן:
SyncManager.Open();

שליחת נתונים לאחר הסנכרון הראשוני
במהלך הריצה של האפליקציה בדרך כלל יש צורך בשליחת עדכונים לשאר ה הpeers , ניתן לעשות זאת באמצעות Update

לדוגמא:
var updateState = new MyStateContainer
{
  StateDictionary = new Dictionary<Guid, MyUserUpdateState>(1)
};
updateState.StateDictionary.Add(id, UserState);
SyncManager.Update(updateState);

הפסקת סנכרון – סגירת ה manager:
הודעה לשאר ה peer שה peer הנוכחי יורד
SyncManager.Close();

יום חמישי, 10 בפברואר 2011

ObservableDictionary of Dr WPF with dispatcher support

אני משתמש הרבה פעמים ב ObservableDictionary של DR WPF

היתה לי בעיה שחוזרת על עצמה כאשר מוסיפים או מורידים איברים מה dictionary ב thread אחר , הוא זורק את ה CollectionChanged / PropertyChanged על אותו thread שבוא נעשה השינוי וזה מה שגורם ל exception:

"InvalidOperationException: The calling thread cannot access this object because a different thread owns it."

כדי לתת תמיכה מתוך ה ObservableDictionary עשיתי לו שינוי קטן שתומך בזריקת ה events על ה dispatcher הנכון:

1. הוספת dispatcher ו DispatcherPriority ל ctor's – כפרמטרים אופציונאליים


public ObservableDictionary(Dispatcher dispatcher = null,DispatcherPriority dispatcherPriority = DispatcherPriority.DataBind)
{
    _dispatcher = dispatcher;
    _dispatcherPriority = dispatcherPriority;
    _keyedEntryCollection = new KeyedDictionaryEntryCollection<TKey>();
}

2. במתודות OnCollectionChanged ו OnPropertyChanged אם יש dispatcher משתמשים בו כדי להעלות את ה  event על הUI thread הנכון.

protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
    var handler = CollectionChanged;
    if (handler != null)
    {
    if (_dispatcher != null)
    {
        if (_dispatcher.CheckAccess())
        {
            handler(this, args);
        }
        else
        {
            _dispatcher.Invoke((Action)(() => handler(this, args)), _dispatcherPriority);
        }
    }
    else
    {
        handler(this, args);
    }
    }
}

ניתן להוריד את הקוד מכאן 

CCCheck has stopped working

פתאום משום מקום כשאני עושה build התחלתי לקבל את ההודעת שגיאה הזאת

image

הבעיה: CodeContracts אצלי עובד עם baseline

image

ולאחר שלקחתי את הפרוייקט מ source control כנראה הוא הפך את חלק מהקבצים ל read only מה שזרק את ה exception (ניתן לראות ב Output)

CodeContracts: Roniz.WCF.P2P.Messages: Unhandled Exception: System.UnauthorizedAccessException: Access to the path …\baseline.xml.new' is denied.

הורדת ה readonly מהקבצים פתרה את הבעיה

יום רביעי, 2 בפברואר 2011

Scalable state synchronization using P2P

בהרבה אפליקציות שמפתחים נדרש לשתף נתונים בין ה server ל client וגם בין מספר clients ל server אחד.

ככל שמספר ה clients גודל – כך העומס על השרת עולה , Azure פותר אולי את הבעיה ברמה הטכנית , אבל יש צורך לשלם על זה...

עם הזמן שמתי לב שיש דפוס כמעט קבוע שחוזר על עצמו בהרבה אפליקציות:

כשה client עולה הוא מסתנכרן מול ה server , וכך כל client נוסף שעולה חוזר על הפעולה.

כשיש שינוי באחד מה clients הוא מעדכן את ה server ומשם יש שתי אופציות:

1. או שה clients עושים pooling ל server כדי לקבל עדכונים.

2. או שה server שולח הודעה (למשל ב WCF ע"י מימוש Pub/Sub) לשאר ה clients.

לכאורה הפתרון השני הוא המועדף – אבל יש איתו מספר בעיות:

1. אם ה clients נמצאים מאחורי NAT או Firewall שמונע מה server שליחת הודעות ב callback אליהם , חייבים להחזיק socket פתוח אל ה server , ואז ככל שכמות ה clients עולה – כך כמות ה sockets הפתוחים בצד ה server.

2. בכמויות גדולות של clients ה server צריך לשלוח את אותה הודעה לכולם מה שיוצר אצלו ג"כ צוואר בקבוק מבחינת ביצועים.

אני מציג כאן תשתית שכתבתי המשתמשת ב PeerChannel (טכנולוגית P2P של מייקרוסופט מבוססת WCF) , כדי לבצע את אותו מנגנון סינכרון , כתבתי גם אפליקציה לטסטים ולדוגמא כיצד לעבוד עם התשתית.

חלק מהרעיונות למדתי מ Kevin Hoffman שפירסם מאמר ב msdn magazine

בעיות קיימות (מקווה לפתור בגרסה נוספת):

1. זמן פתיחה ארוך של ה proxy , וזמן נוסף שעובר לאחר שנוסף peer ל mesh עד ל online event ולתחילת הסנכרון.

2. בסיטואציות מסויימות יכול להיות mesh אחד שמכיל מספר peers אבל מספר קבוצות של peers כך שכל peer חושב שהוא Online ומסונכרן עם כל ה mesh , אבל הסנכרון שלו הוא רק עם התת קבוצה שאליה הוא מחובר , הפיתרון כרגע מבוסס על refresh שנעשה ב interval שניתן לשלוט בו (default 30 שניות בכל peer)

הקוד עדיין בתחילת הדרך , אשמח לקבל הערות ושיפורים.

ניתן להוריד אותו מכאן.

הסבר על האפליקציה לדוגמא:

כל חלון מייצג peer אחד ב mesh , ויש לו name ו data שניתן לערוך:

clip_image001

א. לאחר שלוחצים על connect ה peer פותח channel וה communication status שלו עובר ל opening ולאחר מכן opened, ברגע שה peer עובר לסטטוס opened הוא מכניס ל grid את הפרטים שלו , ועדיין לא מסונכרן מול peerים אחרים.

בדוגמא peer A ו peer B עברו ל Opened והכניסו את המידע על עצמם – עדיין ללא סנכרון , peer C עדיין לא נפתח ונמצא בסטטוס created

clip_image002

ב. אם הוא מוצא peer נוסף הוא עובר לסטטוס online , ומתחיל תהליך סנכרון מול ה peerים האחרים – שולח להם בקשה לסנכרון , מקבל את המידע ומציג אותו ב grid , אותו דבר קורה ב peerים האחרים שעברו למצב online

בדוגמא A ו B במצב Online ומסונכרנים ביניהם

clip_image003

ג. לחיצה על send update of own details תעדכן את כל ה peers האחרים בנתונים שיש באיזור ה user details.

בדוגמא D מעדכן את A B ו C ב "some new data":

clip_image004

ד. לחיצה על disconnect תשלח הודעה ל peerים האחרים על יציאה – והם ינקו את השורה שמייצגת את ה peer שירד.

clip_image005