ä»åŸã¯ãã¯ã©ã¹vã®å¶éãè¶ ããããšãªãããã«ãã¹ã¬ããã®ãã¹ãŠã®åé¡ã解決ã§ãããšèšããŸãã ããã«ãèŠãç®ãããã¯ããã«å°ãªãå€æŽãè¡ããããã®çµæãå®å šã«è§£æ±ºããããã«ãã¹ã¬ããã®åé¡ãå«ãã¯ã©ã¹vã³ãŒãã¯ã50è¡åŒ·ã§æ§æãããŸãïŒ ããã«ã æåã®ããŒãã§èª¬æããã¯ã©ã¹vã®ããŒãžã§ã³ããããããããª50è¡ãæé©ã§ãã ãã®å Žåãã¹ã¬ããåæã®åé¡ã解決ããç¹å®ã®ã³ãŒãã¯20è¡ããããããŸããïŒ
ããã¹ãã®éçšã§ããã®èšäºã®æåŸã«ããå®æããã¯ã©ã¹vãšãã¹ãã®ãªã¹ãããåã ã®è¡ã解æããŸãã
Red Architectureã¯ã©ãã«é©çšã§ããŸããïŒ
ããã§çŽ¹ä»ããäŸã¯ ã Red Architectureã®æŠå¿µå šäœãšåæ§ã«ã å¯èœãªãã¹ãŠã®èšèªãšãã©ãããã©ãŒã ã§äœ¿çšã§ããããã«æäŸãããŠããããšã匷調ããããšæããŸã ã CïŒ/ Xamarinãš.NETãã©ãããã©ãŒã ã¯ãå人çãªå¥œã¿ã«åºã¥ããŠRed Architectureããã¢ã³ã¹ãã¬ãŒã·ã§ã³ããããã«éžã°ããŸããã
ã¯ã©ã¹vã®2ã€ã®ããªã¢ã³ã
ã¯ã©ã¹vã®2ã€ã®ããªã¢ã³ãããããŸãã æ©èœãš1çªç®ã®äœ¿çšæ¹æ³ãåã2çªç®ã®ãªãã·ã§ã³ã¯ãããè€éã«ãªããŸãã ãããããæšæºãã®CïŒ.NETç°å¢ã ãã§ãªããXamarinã®PCLç°å¢ã§ã䜿çšã§ããŸããã€ãŸãã3ã€ã®ãã©ãããã©ãŒã ïŒiOSãAndroidãWindows 10 MobileïŒã§ã®ã¢ãã€ã«éçºãæå³ããŸãã å®éãXamarinãã¬ãŒã ã¯ãŒã¯ã®PCLç°å¢ã§ã¯ã¹ã¬ããã»ãŒãã³ã¬ã¯ã·ã§ã³ã䜿çšã§ããªããããXamarin / PCLã®ã¯ã©ã¹vã®ããŒãžã§ã³ã«ã¯ãã¹ã¬ãããåæããããã®ã³ãŒããå€ãå«ãŸããŸãã ããã¯ããã®èšäºã§æ€èšããããšã§ããã¯ã©ã¹vã®ç°¡æããŒãžã§ã³ïŒãã®èšäºã®æåŸã«ãããŸãïŒã¯ããã«ãã¹ã¬ããã®åé¡ãšãã®è§£æ±ºæ¹æ³ãç解ããäžã§ããŸã䟡å€ããããŸããã
å°ãã®æé©å
ãŸããåºæ¬ã¯ã©ã¹ãåãé€ããã¯ã©ã¹vãèªçµŠèªè¶³ã«ããŸãã ä»ãŸã§äœ¿çšããŠããåºæ¬ã¯ã©ã¹ã®éç¥ã¡ã«ããºã ã¯å¿ èŠãããŸããã ç¶æ¿ãããã¡ã«ããºã ã§ã¯ããã«ãã¹ã¬ããã®åé¡ãæé©ãªæ¹æ³ã§è§£æ±ºããããšã¯ã§ããŸããã ãããã£ãŠã次ã¯ãã³ãã©ãŒé¢æ°ã«ã€ãã³ããéä¿¡ããŸãã
static Dictionary<k, HashSet<NotifyCollectionChangedEventHandler>> handlersMap = new Dictionary<k, HashSet<NotifyCollectionChangedEventHandler>>(); // ... foreach (var handlr in new List<NotifyCollectionChangedEventHandler>(handlersMap[key])) lock(handlr) try { handlr.Invoke(key, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<KeyValuePair<k, object>>(){ new KeyValuePair<k, object>(key, o) }));
foreachã«ãŒãã®AddïŒïŒã¡ãœããã§ãèŠçŽ ãHashSet 'aããListã«ã³ããŒããããã·ã¥ã»ããã§ã¯ãªãã·ãŒããå埩åŠçããŸãã handlersMap [key]åŒã«ãã£ãŠè¿ãããå€ã¯ãmïŒïŒãhïŒïŒãªã©ã®ãããªãã¯ç¶æ å€æŽã¡ãœããããã¢ã¯ã»ã¹å¯èœãªã°ããŒãã«å€æ°ã§ããããããããè¡ãå¿ èŠããããŸãããããã£ãŠãhandlersMap [key]åŒã«ãã£ãŠè¿ãããHashMapã¯AddïŒïŒã¡ãœããã§ã®å埩äžã«å¥ã®ã¹ã¬ããã«ãã£ãŠå€æŽãããŸããforeachå ã®ã³ã¬ã¯ã·ã§ã³ã®å埩ãå®äºãããŸã§ããã®ïŒã³ã¬ã¯ã·ã§ã³ïŒå€æŽã¯çŠæ¢ãããŠãããããå®è¡æã«å®è¡ãããŸãã ããããã°ããŒãã«å€æ°ã§ã¯ãªããã°ããŒãã«HashSetã®èŠçŽ ãã³ããŒãããListãå埩ã§ã眮æãããçç±ã§ãã
ãããããã®ä¿è·ã¯ååã§ã¯ãããŸããã åŒã§
new List<NotifyCollectionChangedEventHandler>(handlersMap[key])
ã³ããŒæäœã¯ãhandlersMap [key]ã®å€ïŒããã·ã¥ã»ããïŒã«æé»çã«é©çšãããŸãã ã³ããŒæäœã®éå§ãšçµäºã®éã«ãä»ã®ã¹ã¬ãããã³ããŒãããããã·ã¥ã»ããã®èŠçŽ ãè¿œå ãŸãã¯åé€ããããšãããšãããã¯ééããªãåé¡ãåŒãèµ·ãããŸãã ãããã£ãŠãforeachã®éå§çŽåã«ãã®ããã·ã¥ã«ããã¯ïŒMonitor.EnterïŒhandlersMap [key]ïŒïŒãèšå®ããŸãã
Monitor.Enter(handlersMap[key]); foreach (var handlr in new List<NotifyCollectionChangedEventHandler>(handlersMap[key])) { // ...
ããªãªãŒã¹ãïŒMonitor.ExitïŒhandlersMap [ããŒ]ïŒïŒforeachã«ãŒãã«å ¥ã£ãçŽåŸã«ããã¯ãã
foreach (var handlr in new List<NotifyCollectionChangedEventHandler>(handlersMap[key])) { if (Monitor.IsEntered(handlersMap[key])) { Monitor.PulseAll(handlersMap[key]); Monitor.Exit(handlersMap[key]); } // ...
Monitorãªããžã§ã¯ãã®ã«ãŒã«ã«åŸã£ãŠãEnterïŒïŒã®åŒã³åºãã®æ°ã¯ExitïŒïŒã®åŒã³åºãã®æ°ã«å¯Ÿå¿ããå¿ èŠããããããããã¯ãã€ã³ã¹ããŒã«ãããå Žåã«1ã€ã ããçµäºããããšã確èªããifãã§ãã¯ïŒMonitor.IsEnteredïŒhandlersMap [key]ïŒïŒããããŸãåãforeachã«ãŒãã®æåã®å埩ã®éå§æã è¡Monitor.ExitïŒhandlersMap [key]ïŒã®çŽåŸã«ãhaslers handlersMap [key]ãä»ã®ã¹ã¬ããã§åã³äœ¿çšå¯èœã«ãªããŸãã ãããã£ãŠãããã·ã¥ããã¯ãå¯èœãªæå°æéã«å¶éããŸãããã®å Žåãããã·ã¥ã¯æåéãäžæçã«ãããã¯ããããšèšããŸãã
foreachã«ãŒãã®çŽåŸã«ãããã¯è§£é€ã³ãŒãã®ç¹°ãè¿ãã衚瀺ãããŸãã
// ... if (Monitor.IsEntered(handlersMap[key])) { Monitor.PulseAll(handlersMap[key]); Monitor.Exit(handlersMap[key]); } // ...
ãã®ã³ãŒãã¯ãforeachã«å埩ããªãå Žåã«å¿ èŠã§ããããã¯ãããŒã®1ã€ã«å¯ŸããŠã察å¿ããããã·ã¥ã»ããã«åäžã®ãã³ãã©ãŒããªãå Žåã«å¯èœã§ãã
次ã®ã³ãŒãã«ã¯è©³çŽ°ãªèª¬æãå¿ èŠã§ãã
lock(handlr) try { // ...
å®éãRed Architectureã®æŠå¿µã§ã¯ãã¯ã©ã¹vã®å€éšã§äœæãããã¹ã¬ããã®åæãå¿ èŠãšãããªããžã§ã¯ãã¯ãã³ãã©ãŒé¢æ°ã®ã¿ã§ãã ãã³ãã©ãŒãé¢æ°ãåŒã³åºãã³ãŒãã管çã§ããªãã£ãå Žåãåãã³ãã©ãŒã®ããã«ããã§ã³ã¹ãããå¿ èŠããããŸã
void OnEvent(object sender, NotifyCollectionChangedEventArgs e) { lock(OnEvent); // unlock(OnEvent); }
lockïŒïŒunlockïŒïŒè¡ã®éã«æçšãªã¡ãœããã³ãŒããé 眮ãããŠããããšã«æ³šæããŠãã ããã å€éšã®ãã³ãã©ãŒå ã®ããŒã¿ãå€æŽãããå ŽåãlockïŒïŒããã³unlockïŒïŒãè¿œå ããå¿ èŠããããŸãã åæã«ããã®é¢æ°ã«å ¥ããããŒã¯ãæ··variablesãšããé åºã§å€éšå€æ°ã®å€ãå€æŽããããã§ãã
ãããã代ããã«ãããã°ã©ã å šäœã«1è¡ïŒããã¯ïŒhandlrïŒïŒãè¿œå ããvã¯ã©ã¹å ã§ãããå€éšã®äœã觊ããã«è¡ããŸããïŒ vã¯ã©ã¹ã®å®è£ ã«ããã1ã€ã®ã¹ã¬ããã®ã¿ããã®ç¹å®ã®ãã³ãã©ãŒã«å ¥ãããšãä¿èšŒãããä»ã®ã¹ã¬ããã¯ããã¯ïŒããã³ãã©ãŒãïŒã«ãç«ã¡ãããã®äœæ¥ãå®äºãããŸã§åŸ æ©ãããããã¹ã¬ããã®å®å šæ§ãèããã«ä»»æã®æ°ã®ãã³ãã©ãŒé¢æ°ãäœæã§ããŸããããå ¥åããåã®ã¹ã¬ããã®ãã³ãã©ãŒã
foreachãforïŒ;;ïŒããã³ãã«ãã¹ã¬ãã
ãã¹ãã®ãªã¹ãïŒèšäºã®æåŸïŒã«ã¯ã2ã€ã®ã¹ã¬ããããã®ã¡ãœããã«å ¥ãããããã£ãŠforïŒ;;ïŒã«ãŒãã«å ¥ããšãã«forïŒ;;ïŒã«ãŒãã®åäœããã§ãã¯ããforeachTestïŒstring [] aïŒã¡ãœããããããŸãã 以äžã¯ããã®ã¡ãœããã®åºåã®å¯èœãªéšåã§ãã
// ...
ãïŒstring20
ãïŒstring21
ãïŒstring22
ãïŒastring38
ãïŒastring39
ãïŒstring23
ãïŒstring24
ãïŒastring40
ãïŒastring41
ãïŒstring25
ãïŒastring42
ãïŒstring26
ãïŒastring43
ãïŒastring44
ãïŒstring27
ãïŒastring45
ãïŒstring28
// ...
ãstringãè¡ãšãastringãè¡ã®åºåãæ··åšããŠããã«ãããããããåè¡ã®æ°å€ã®æ¥å°ŸèŸã¯é çªã«äžŠãã§ããŸãã åè¡ãåºåããããã«ãããŒã«ã«å€æ°iãçã«ãªããŸãã ãã®çµè«ã¯ãforïŒ;;ïŒãžã®2ã€ã®ã¹ã¬ããã®åæå ¥åãå®å šã§ããããšã瀺åããŠããŸãã ãããããforïŒ;;ïŒæ§é ã®ãã¬ãŒã ã¯ãŒã¯ã§å®£èšããããã¹ãŠã®å€æ°ãããšãã°å€æ°int iã¯ãforïŒ;;ïŒã«å ¥ã£ãã¹ããªãŒã ã®ã¹ã¿ãã¯äžã«äœæãããŸãã ãã®ãããïŒ;;ïŒã®å éšã§äœæãããå€æ°ãžã®ã¢ã¯ã»ã¹ã¯ããæåãåæãå¿ èŠãšããŸããããªããªãããããã¯ãã¹ã¿ãã¯ãäœæãããã¹ã¬ããã§ã®ã¿å©çšå¯èœã ããã§ãã ããã¯ãCïŒãš.NETãã©ãããã©ãŒã ã®å Žåã§ãã ä»ã®èšèªã§ã¯ãå¯èœæ§ã¯äœãã§ãããç°ãªãåäœãããå¯èœæ§ãããããããã®ãããªãã¹ãã¯äžèŠã§ã¯ãããŸããã
try ... catchã¯äŸå€ã§ã¯ãªãæšæºã§ã
try ... catchäžèŠããã®æ§é ã¯äžèŠã«æããŸãããéèŠã§ãã handlr.InvokeïŒïŒã®åŒã³åºãæã«ãhandlrãå®çŸ©ããããªããžã§ã¯ããç Žå£ãããç¶æ³ããç§ãã¡ãä¿è·ããããšãç®çãšããŠããŸãã ãªããžã§ã¯ãã®ç Žæ£ã¯ãè¡éã§ãã€ã§ãå¥ã®ã¹ã¬ãããŸãã¯ã¬ããŒãžã³ã¬ã¯ã¿ãŒã«ãã£ãŠå®è¡ã§ããŸãã
foreach (var handlr in new List<NotifyCollectionChangedEventHandler>(handlersMap[key]))
ãããŠ
handlr.Invoke();
catchãããã¯ã§ããäŸå€åŠçã§ã¯ããã³ãã©ãŒãnullïŒåé€æžã¿ïŒãªããžã§ã¯ããåç §ããŠãããã©ããã確èªãããã³ãã©ãŒãªã¹ãããåçŽã«åé€ããŸãã
lock (handlr) try { handlr.Invoke(key, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<KeyValuePair<k, object>>(){ new KeyValuePair<k, object>(key, o) })); #if __tests__ /* check modification of global collection of handlers for a key while iteration through its copy */ handlersMap[key].Add((object sender, NotifyCollectionChangedEventArgs e) => { }); #endif } catch (Exception e) { // because exception can be thrown inside handlr.Invoke(), but before handler was destroyied. if (ReferenceEquals(null,handlr) && e is NullReferenceException) // handler invalid, remove it m(handlr); else // exception in handler's body throw e; }
ã¯ã©ã¹vã®åæå
éçã³ã³ã¹ãã©ã¯ã¿ãŒã¯ãCïŒã®ç¹åŸŽã®1ã€ã§ãã çŽæ¥åŒã³åºãããšã¯ã§ããŸããã ãã®ã¯ã©ã¹ã®æåã®ãªããžã§ã¯ããäœæããåã«ãäžåºŠã ãèªåçã«åŒã³åºãããŸãã ããã䜿çšããŠhandlersMapãåæåããŸã-kã®ãã¹ãŠã®ããŒã«å¯ŸããŠãåããŒã®ãã³ãã©ãŒé¢æ°ãæ ŒçŽããããã®ç©ºã®HashSetã䜿çšããæºåãããŸãã ä»ã®èšèªã«éçã³ã³ã¹ãã©ã¯ã¿ãŒããªãå Žåããªããžã§ã¯ããåæåããã¡ãœãããé©ããŠããŸãã
static v() { foreach (ke in Enum.GetValues(typeof(k))) handlersMap[e] = new HashSet<NotifyCollectionChangedEventHandler>(); new Tests().run(); }
ã¹ã¬ããã®å®å šã§ãªãã³ã¬ã¯ã·ã§ã³ãã©ããããïŒ
CïŒã¯ã©ã¹ã®HashSetã¯ãè€æ°ã®ã¹ã¬ããïŒã¹ã¬ããã»ãŒãã§ã¯ãªãïŒããã®å€æŽæã«åæãæäŸããªãããããã®ãªããžã§ã¯ãã®å€æŽãã€ãŸãèŠçŽ ã®åé€ãšè¿œå ãåæããå¿ èŠããããŸãã ãã®å Žåãã¯ã©ã¹vã®ã¡ãœããmïŒïŒãhïŒïŒã§èŠçŽ ãåé€/è¿œå ããæäœã®çŽåã«ã1ã€ã®è¡ããã¯ïŒhandlersMap [key]ïŒãè¿œå ããã ãã§ååã§ãã ãã®å ŽåããããŒããããã¯ãããªããžã§ã¯ãã¯ããã®ç¹å®ã®ããŒã«é¢é£ä»ããããHashMapãªããžã§ã¯ãã«ãªããŸãã ããã«ããã1ã€ã®ã¹ã¬ããã®ã¿ã§ãã®ç¹å®ã®ããã·ã¥ã»ãããå€æŽã§ããŸãã
ãã«ãã¹ã¬ããã®å¯äœçš
ãã«ãã¹ã¬ããã®ãå¯äœçšãã®ããã€ãã«èšåãã䟡å€ããããŸãã ç¹ã«ããã³ãã©ãŒé¢æ°ã®ã³ãŒãã¯ãå Žåã«ãã£ãŠã¯ãã³ãã©ãŒé¢æ°ã®ããµãã¹ã¯ã©ã€ã解é€ãåŸã«ã€ãã³ããåä¿¡ããåŸã«åŒã³åºããããšããäºå®ã«åããŠæºåããå¿ èŠããããŸãã ã€ãŸããmïŒããŒããã³ãã©ãŒïŒãã³ãã©ãŒããã°ããïŒãããã1ç§æªæºïŒåŒã³åºããåŸã§ãåŒã³åºãããšãã§ããŸãã ããã¯ãmïŒïŒã¡ãœããã§handlersMap [key] .RemoveïŒhandlerïŒãåŒã³åºãããšãã«ããã®ãã³ãã©ãŒãforeachè¡ã®å¥ã®ã¹ã¬ããã«ãã£ãŠæ¢ã«ã³ããŒãããŠããå¯èœæ§ãããããã§ãïŒæ°ãããªã¹ãã®var handlrïŒhandlersMap [key]ïŒïŒ ãããã³mïŒïŒã¡ãœããã§åé€ãããåŸãã¯ã©ã¹vã®AddïŒïŒã¡ãœããã§åŒã³åºãããŸãã
è€éãªåé¡ã解決ããç°¡åãªã«ãŒã«
çµè«ãšããŠãç§ã¯å€åãªéçºè ã§ãããããããã¯ã®äœ¿çšã«é¢ããåæã«éåããªããšããäºå®ã«æ³šæãåèµ·ããããšæããŸãã ç¹ã«ããã®ãããªå¥çŽã¯ããã®ããŒãžã®docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statementã®åèã»ã¯ã·ã§ã³ã«ãªã¹ããããŠããŸãã CïŒã ãã§ãªãããã¹ãŠã®èšèªã«å ±éã§ãã ãããã®å¥çŽã®æ¬è³ªã¯æ¬¡ã®ãšããã§ãã
- ãããªãã¯åãããã¯ã®ãªããžã§ã¯ããšããŠäœ¿çšããªãã§ãã ããã
ããã¯ã«ã¯2çš®é¡ã®ãªããžã§ã¯ãã䜿çšããäž¡æ¹ãšããã©ã€ããŒãã§ãã æåã®ã¿ã€ãã¯ãã¯ã©ã¹vã«ãã©ã€ããŒããªHashSetãªããžã§ã¯ãã§ãã 2çªç®ã®åã¯ãé¢æ°ãã³ãã©ãŒåã®ãªããžã§ã¯ãã§ãã é¢æ°ãã³ãã©ã¯ããããã宣èšãããããã䜿çšããŠã€ãã³ããåä¿¡ãããã¹ãŠã®ãªããžã§ã¯ãã§ãã©ã€ããŒããšããŠå®£èšãããŸãã Red Architectureã®å Žåãvã¯ã©ã¹ã®ã¿ããã³ãã©ãŒãçŽæ¥åŒã³åºãå¿ èŠãããããã以å€ã¯äœãåŒã³åºããŸããã
ãªã¹ã
以äžã¯ãvã¯ã©ã¹ãšTestsã¯ã©ã¹ã®å®æããã³ãŒãã§ãã CïŒã§ã¯ãããããã³ããŒããŠçŽæ¥äœ¿çšã§ããŸãã ãã®ã³ãŒããä»ã®èšèªã«ç¿»èš³ããããšã¯ãããªãã«ãšã£ãŠå°ããªæ¥œããä»äºã«ãªããŸãã
以äžã¯ãããŠãããŒãµã«ãã¯ã©ã¹vã®ã³ãŒãã§ããããã¯ãXamarin / CïŒãã©ãããã©ãŒã ã«åºã¥ãã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³ãããžã§ã¯ãã§ã䜿çšã§ããŸãã
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Threading; namespace Common { public enum k {OnMessageEdit, MessageEdit, MessageReply, Unused, MessageSendProgress, OnMessageSendProgress, OnIsTyping, IsTyping, MessageSend, JoinRoom, OnMessageReceived, OnlineStatus, OnUpdateUserOnlineStatus } public class v { static Dictionary<k, HashSet<NotifyCollectionChangedEventHandler>> handlersMap = new Dictionary<k, HashSet<NotifyCollectionChangedEventHandler>>(); public static void h(k[] keys, NotifyCollectionChangedEventHandler handler) { foreach (var key in keys) lock(handlersMap[key]) handlersMap[key].Add(handler); } public static void m(NotifyCollectionChangedEventHandler handler) { foreach (k key in Enum.GetValues(typeof(k))) lock(handlersMap[key]) handlersMap[key].Remove(handler); } public static void Add(k key, object o) { Monitor.Enter(handlersMap[key]); foreach (var handlr in new List<NotifyCollectionChangedEventHandler>(handlersMap[key])) { if (Monitor.IsEntered(handlersMap[key])) { Monitor.PulseAll(handlersMap[key]); Monitor.Exit(handlersMap[key]); } lock (handlr) try { handlr.Invoke(key, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<KeyValuePair<k, object>>(){ new KeyValuePair<k, object>(key, o) })); #if __tests__ /* check modification of global collection of handlers for a key while iteration through its copy */ handlersMap[key].Add((object sender, NotifyCollectionChangedEventArgs e) => { }); #endif } catch (Exception e) { // because exception can be thrown inside handlr.Invoke(), but before handler was destroyied. if (ReferenceEquals(null,handlr) && e is NullReferenceException) // handler invalid, remove it m(handlr); else // exception in handler's body throw e; } } if (Monitor.IsEntered(handlersMap[key])) { Monitor.PulseAll(handlersMap[key]); Monitor.Exit(handlersMap[key]); } } static v() { foreach (ke in Enum.GetValues(typeof(k))) handlersMap[e] = new HashSet<NotifyCollectionChangedEventHandler>(); new Tests().run(); } } }
以äžã¯ããæšæºãCïŒ.NETãã©ãããã©ãŒã ã§äœ¿çšã§ãããåçŽåããããã¯ã©ã¹vã®ã³ãŒãã§ãã ããŠãããŒãµã«ãã«ãŠã³ã¿ãŒããŒããšã®å¯äžã®éãã¯ãHashMapã®ä»£ããã«ConcurrentBagã³ã¬ã¯ã·ã§ã³ã䜿çšããŠããããšã§ããHashMapã¯ãèªåã«ã¢ã¯ã»ã¹ãããšãã«ããã«äœ¿çšã§ãããããŒã®åæãæäŸããã¿ã€ãã§ãã HashSetã®ä»£ããã«ConcurrentBagã䜿çšãããšãã¹ã¬ãããåæããã³ãŒãã®ã»ãšãã©ãã¯ã©ã¹vããåé€ã§ããŸãã
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Collections.Concurrent; using System.Threading; namespace Common { public enum k { OnMessageEdit, MessageEdit, MessageReply, Unused, MessageSendProgress, OnMessageSendProgress, OnIsTyping, IsTyping, MessageSend, JoinRoom, OnMessageReceived, OnlineStatus, OnUpdateUserOnlineStatus } public class v { static Dictionary<k, ConcurrentBag<NotifyCollectionChangedEventHandler>> handlersMap = new Dictionary<k, ConcurrentBag<NotifyCollectionChangedEventHandler>>(); public static void h(k[] keys, NotifyCollectionChangedEventHandler handler) { foreach (var key in keys) handlersMap[key].Add(handler); } public static void m(NotifyCollectionChangedEventHandler handler) { foreach (k key in Enum.GetValues(typeof(k))) handlersMap[key].Remove(handler); } public static void Add(k key, object o) { foreach (var handlr in new List<NotifyCollectionChangedEventHandler>(handlersMap[key])) { lock (handlr) try { handlr.Invoke(key, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<KeyValuePair<k, object>>(){ new KeyValuePair<k, object>(key, o) })); #if __tests__ /* check modification of global collection of handlers for a key while iteration through its copy */ handlersMap[key].Add((object sender, NotifyCollectionChangedEventArgs e) => { }); #endif } catch (Exception e) { // because exception can be thrown inside handlr.Invoke(), but before handler was destroyied. if (ReferenceEquals(null,handlr) && e is NullReferenceException) // handler invalid, remove it m(handlr); else // exception in handler's body throw e; } } } static v() { foreach (ke in Enum.GetValues(typeof(k))) handlersMap[e] = new ConcurrentBag<NotifyCollectionChangedEventHandler>(); new Tests().run(); } } }
以äžã«ãTestsã¯ã©ã¹ã®ã³ãŒãã瀺ããŸãããã®ã¯ã©ã¹ã¯ãvã®ãã«ãã¹ã¬ãã䜿çšãšãã³ãã©ãŒé¢æ°ããã¹ãããŸãã ã³ã¡ã³ãã«æ³šæããŠãã ããã 圌ãã¯ãã¹ããšãã¹ãã³ãŒããã©ã®ããã«æ©èœãããã«ã€ããŠå€ãã®æçšãªæ å ±ãæã£ãŠããŸãã
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Threading.Tasks; using System.Diagnostics; using System.Linq; namespace ChatClient.Core.Common { class DeadObject { void OnEvent(object sender, NotifyCollectionChangedEventArgs e) { var newItem = (KeyValuePair<k, object>)e.NewItems[0]; Debug.WriteLine(String.Format("~ OnEvent() of dead object: key: {0} value: {1}", newItem.Key.ToString(), newItem.Value)); } public DeadObject() { vh(new k[] { k.OnlineStatus }, OnEvent); } ~DeadObject() { // Accidentally we forgot to call vm(OnEvent) here, and now v.handlersMap contains reference to "dead" handler } } public class Tests { void OnEvent(object sender, NotifyCollectionChangedEventArgs e) { var newItem = (KeyValuePair<k, object>)e.NewItems[0]; Debug.WriteLine(String.Format("~ OnEvent(): key: {0} value: {1}", newItem.Key.ToString(), newItem.Value)); if (newItem.Key == k.Unused) { // v.Add(k.Unused, "stack overflow crash"); // reentrant call in current thread causes stack overflow crash. Deadlock doesn't happen, because lock mechanism allows reentrancy for a thread that already has a lock on a particular object // Task.Run(() => v.Add(k.Unused, "deadlock")); // the same call in a separate thread don't overflow, but causes infinite recursive loop } } void OnEvent2(object sender, NotifyCollectionChangedEventArgs e) { var newItem = (KeyValuePair<k, object>)e.NewItems[0]; Debug.WriteLine(String.Format("~ OnEvent2(): key: {0} value: {1}", newItem.Key.ToString(), newItem.Value)); } void foreachTest(string[] a) { for (int i = 0; i < a.Length; i++) { Debug.WriteLine(String.Format("~ : {0}{1}", a[i], i)); } } async void HandlersLockTester1(object sender, NotifyCollectionChangedEventArgs e) { var newItem = (KeyValuePair<k, object>)e.NewItems[0]; Debug.WriteLine(String.Format("~ HandlersLockTester1(): key: {0} value: {1}", newItem.Key.ToString(), newItem.Value)); await Task.Delay(300); } async void HandlersLockTester2(object sender, NotifyCollectionChangedEventArgs e) { var newItem = (KeyValuePair<k, object>)e.NewItems[0]; Debug.WriteLine(String.Format("~ HandlersLockTester2(): key: {0} value: {1}", newItem.Key.ToString(), newItem.Value)); } public async void run() { // Direct call for garbage collector - should be called for testing purposes only, not recommended for a business logic of an application GC.Collect(); /* * == test v.Add()::foreach (var handlr in new List<NotifyCollectionChangedEventHandler>(handlersMap[key])) * for two threads entering the foreach loop at the same time and iterating handlers only of its key */ Task t1 = Task.Run(() => { v.Add(k.OnMessageReceived, "this key"); }); Task t2 = Task.Run(() => { v.Add(k.MessageEdit, "that key"); }); // wait for both threads to complete before executing next test await Task.WhenAll(new Task[] { t1, t2 }); /* For now DeadObject may be already destroyed, so we may test catch block in v class */ v.Add(k.OnlineStatus, "for dead object"); /* test reentrant calls - causes stack overflow or infinite loop, depending on code at OnEvent::if(newItem.Key == k.Unused) clause */ v.Add(k.Unused, 'a'); /* testing foreach loop entering multiple threads */ var s = Enumerable.Repeat("string", 200).ToArray(); var n = Enumerable.Repeat("astring", 200).ToArray(); t1 = Task.Run(() => { foreachTest(s); }); t2 = Task.Run(() => { foreachTest(n); }); // wait for both threads to complete before executing next test await Task.WhenAll(new Task[] { t1, t2 }); /* testing lock(handlr) in Add() method of class v */ vh(new k[] { k.IsTyping }, HandlersLockTester1); vh(new k[] { k.JoinRoom }, HandlersLockTester2); // line 1 Task.Run(() => { v.Add(k.IsTyping, "first thread for the same handler"); }); // line 2 Task.Run(() => { v.Add(k.IsTyping, "second thread for the same handler"); }); // line below will MOST OF THE TIMES complete executing before the line 2 above, because line 2 will wait completion of line 1 // since both previous lines 1 and 2 are calling the same handler, access to which is synchronized by lock(handlr) in Add() method of class v Task.Run(() => { v.Add(k.JoinRoom, "third thread for other handler"); }); } public Tests() { // add OnEvent for each key vh(new k[] { k.OnMessageReceived, k.MessageEdit, k.Unused }, OnEvent); // add OnEvent2 for each key vh(new k[] { k.Unused, k.OnMessageReceived, k.MessageEdit }, OnEvent2); /* == test try catch blocks in v class, when handler is destroyed before handlr.Invoke() called */ var ddo = new DeadObject(); // then try to delete object, setting its value to null. We are in a managed environment, so we can't directly manage life cicle of an object. ddo = null; } } }
ãã³ãã©ãŒé¢æ°ãç»é²ããã³ãŒããšããã®ãããªã¯ã©ã¹vã®ãã³ãã©ãŒé¢æ°èªäœã¯ã次ã®ããã«ãªããŸãã
ãã³ãã©ãŒæ©èœç»é²ã³ãŒã
// add OnEvent for each key vh(new k[] { k.OnMessageReceived, k.MessageEdit, k.Unused }, OnEvent);
ãã³ãã©ãŒæ©èœã³ãŒã
void OnEvent(object sender, NotifyCollectionChangedEventArgs e) { var newItem = (KeyValuePair<k, object>)e.NewItems[0]; Debug.Write("~ OnEvent(): key {0} value {1}", newItem.Key.ToString(), newItem.Value); }
Red Architectureã®äžè¬çãªèª¬æã¯ãã¡ãã§ãã