Aracılığıyla paylaş


Xamarin.Mac uygulamasında yerel kilitlenmede hata ayıklama

Genel bakış

Bazen programlama hataları yerel Objective-C çalışma zamanında kilitlenmelere neden olabilir. C# özel durumlarından farklı olarak, bunlar kodunuzda düzeltmek için bakabileceğiniz belirli bir satırı işaret etmemektedir. Bazen bulmaları ve düzeltmeleri önemsiz olabilir, bazen de izlemeleri son derece zor olabilir.

Şimdi birkaç gerçek yerel kilitlenme örneğini gözden geçirelim ve bir göz atalım.

Örnek 1: Onaylama hatası

Basit bir test uygulamasında kilitlenmenin ilk birkaç satırı aşağıdadır (bu bilgiler Uygulama Çıkış Bölmesi'nde olacaktır):

2014-10-15 16:18:02.364 NSOutlineViewHottness[79111:1304993] *** Assertion failure in -[NSTableView _uncachedRectHeightOfRow:], /SourceCache/AppKit/AppKit-1343.13/TableView.subproj/NSTableView.m:1855
2014-10-15 16:18:02.364 NSOutlineViewHottness[79111:1304993] NSTableView variable rowHeight error: The value must be > 0 for row 0, but the delegate <NSOutlineViewHottness_HotnessViewDelegate: 0xaa01860> gave -1.000.
2014-10-15 16:18:02.378 NSOutlineViewHottness[79111:1304993] *** Assertion failure in -[NSTableView _uncachedRectHeightOfRow:], /SourceCache/AppKit/AppKit-1343.13/TableView.subproj/NSTableView.m:1855
2014-10-15 16:18:02.378 NSOutlineViewHottness[79111:1304993] NSTableView variable rowHeight error: The value must be > 0 for row 0, but the delegate <NSOutlineViewHottness_HotnessViewDelegate: 0xaa01860> gave -1.000.
2014-10-15 16:18:02.381 NSOutlineViewHottness[79111:1304993] (
  0   CoreFoundation                      0x91888343 __raiseError + 195
  1   libobjc.A.dylib                     0x9a5e6a2a objc_exception_throw + 276
  2   CoreFoundation                      0x918881ca +[NSException raise:format:arguments:] + 138
  3   Foundation                          0x950742b1 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 118
  4   AppKit                              0x975db476 -[NSTableView _uncachedRectHeightOfRow:] + 373
  5   AppKit                              0x975db2f8 -[_NSTableRowHeightStorage _uncachedRectHeightOfRow:] + 143
  6   AppKit                              0x975db206 -[_NSTableRowHeightStorage _cacheRowHeights] + 167
  7   AppKit                              0x975db130 -[_NSTableRowHeightStorage _createRowHeightsArray] + 226
  8   AppKit                              0x975b5851 -[_NSTableRowHeightStorage _ensureRowHeights] + 73
  9   AppKit                              0x975b5790 -[_NSTableRowHeightStorage computeTableHeightForNumberOfRows:] + 89
  10  AppKit                              0x975b4c38 -[NSTableView _totalHeightOfTableView] + 220

Sayı ön ekli satırlar, yerel yığın izlemesidir. Bundan, kilitlenmenin satır yüksekliklerini işleyen bir yerde NSTableView meydana geldiğini görebilirsiniz. Ardından NSAssertionHandler bir NSException (objc_exception_throw) tetikler ve Assertion hatasını görürüz:

rowHeight error: The value must be > 0 for row 0, but the delegate
<NSOutlineView_ViewDelegate: 0xaa01860> gave -1.000

Bunu gördüğünüzde, bazı NSOutlineViewDelegate yöntemlerin negatif bir sayı döndürdüğünü görebilirsiniz. Sorun şuydu:

public override nfloat GetRowHeight (NSTableView tableView, nint row)
{
    return -1;
}

Örnek 2: Geri arama hiçbir yerin ortasına atladı

Stacktrace:

 at <unknown> <0xffffffff>
 at (wrapper managed-to-native) MonoMac.AppKit.NSApplication.NSApplicationMain (int,string[]) <IL 0x000a4, 0xffffffff>
 at MonoMac.AppKit.NSApplication.Main (string[]) [0x00041] in /Users/donblas/Programming/xamcore-master/src/AppKit/NSApplication.cs:107
 at NSOutlineViewHottness.MainClass.Main (string[]) [0x00007] in /Users/donblas/Programming/Local/NSOutlineViewHottness/NSOutlineViewHottness/Main.cs:14
 at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <IL 0x00050, 0xffffffff>

Native stacktrace:

Debug info from gdb:

(lldb) command source -s 0 '/tmp/mono-gdb-commands.qrHllW'
Executing commands in '/private/tmp/mono-gdb-commands.qrHllW'.
(lldb) process attach --pid 79229
Process 79229 stopped
Executable module set to "/Users/donblas/Programming/Local/NSOutlineViewHottness/NSOutlineViewHottness/bin/Debug/NSOutlineViewHottness.app/Contents/MacOS/NSOutlineViewHottness".
Architecture set to: i386-apple-macosx.
(lldb) thread list
Process 79229 stopped
* thread #1: tid = 0x142776, 0x9af75e1a libsystem_kernel.dylib`__wait4 + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
 thread #2: tid = 0x142790, 0x9af768d2 libsystem_kernel.dylib`kevent64 + 10, queue = 'com.apple.libdispatch-manager'
 thread #3: tid = 0x142792, 0x9af75e6e libsystem_kernel.dylib`__workq_kernreturn + 10
 thread #4: tid = 0x142794, 0x9af6fa6a libsystem_kernel.dylib`semaphore_wait_trap + 10
 thread #5: tid = 0x142795, 0x9af75772 libsystem_kernel.dylib`__recvfrom + 10
 thread #6: tid = 0x142799, 0x9af75e6e libsystem_kernel.dylib`__workq_kernreturn + 10
 thread #7: tid = 0x14279a, 0x9af75e6e libsystem_kernel.dylib`__workq_kernreturn + 10
 thread #8: tid = 0x14279b, 0x9af75e6e libsystem_kernel.dylib`__workq_kernreturn + 10
 thread #9: tid = 0x1427f8, 0x9af75e6e libsystem_kernel.dylib`__workq_kernreturn + 10
 thread #10: tid = 0x1427fe, 0x9af6fa2e libsystem_kernel.dylib`mach_msg_trap + 10
(lldb) thread backtrace all
* thread #1: tid = 0x142776, 0x9af75e1a libsystem_kernel.dylib`__wait4 + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
 * frame #0: 0x9af75e1a libsystem_kernel.dylib`__wait4 + 10
   frame #1: 0x986bfb25 libsystem_c.dylib`waitpid$UNIX2003 + 48
   frame #2: 0x028ba36d libmono-2.0.dylib`mono_handle_native_sigsegv(signal=11, ctx=0x03115fe0) + 541 at mini-exceptions.c:2323
   frame #3: 0x0290a8bb libmono-2.0.dylib`mono_arch_handle_altstack_exception(sigctx=<unavailable>, fault_addr=<unavailable>, stack_ovf=0) + 155 at exceptions-x86.c:1159
   frame #4: 0x0280b4fd libmono-2.0.dylib`mono_sigsegv_signal_handler(_dummy=<unavailable>, info=<unavailable>, context=<unavailable>) + 445 at mini.c:6861
   frame #5: 0x91ef403b libsystem_platform.dylib`_sigtramp + 43
   frame #6: 0x9a5dd0bd libobjc.A.dylib`objc_msgSend + 45
   frame #7: 0x96bcec03 libsystem_trace.dylib`_os_activity_initiate + 89
   frame #8: 0x9773ba91 AppKit`-[NSApplication sendAction:to:from:] + 548
   frame #9: 0x9773b82d AppKit`-[NSControl sendAction:to:] + 102
   frame #10: 0x97934d36 AppKit`__26-[NSCell _sendActionFrom:]_block_invoke + 176
   frame #11: 0x96bcec03 libsystem_trace.dylib`_os_activity_initiate + 89
   frame #12: 0x97787975 AppKit`-[NSCell _sendActionFrom:] + 161
   frame #13: 0x979188ea AppKit`-[NSButtonCell _sendActionFrom:] + 55
   frame #14: 0x979366e6 AppKit`__48-[NSCell trackMouse:inRect:ofView:untilMouseUp:]_block_invoke965 + 43
   frame #15: 0x96bcec03 libsystem_trace.dylib`_os_activity_initiate + 89
   frame #16: 0x977a3d00 AppKit`-[NSCell trackMouse:inRect:ofView:untilMouseUp:] + 2815
   frame #17: 0x977a2df4 AppKit`-[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 524
   frame #18: 0x977a233b AppKit`-[NSControl mouseDown:] + 762
   frame #19: 0x97cbc112 AppKit`-[_NSThemeWidget mouseDown:] + 378
   frame #20: 0x97d36d74 AppKit`-[NSWindow _reallySendEvent:] + 12353
   frame #21: 0x977201f9 AppKit`-[NSWindow sendEvent:] + 409
   frame #22: 0x976cdc67 AppKit`-[NSApplication sendEvent:] + 4679
   frame #23: 0x9754807c AppKit`-[NSApplication run] + 1003

Bu, izlenmesi çok daha zor olan bir sorundur. Yönetilen yığın izlemesinin en üstünde veya MonoMac.ObjCRuntime.Runtime.GetNSObject (IntPtr ptr) öğesini gördüğünüzdeat <unknown> <0xffffffff>, bazı yönetilen kodları atık toplanmış bir nesneyle yürütmeye çalıştığımızı gösterir. Yerel yığın izlemesi içinde NSCell _sendActionFrom gösterildiğindentrackMouse:inRect:ofView:untilMouseUp, bir tıklama olayını işleyen bir yerdeyiz ve C# ile geri çağrı yapmaya çalışıyoruz ve ölüyoruz.

Genel olarak, bu gibi hataların izlenmesi zordur. Sorunu yeniden üretilebilir hale getirmek için bu sorunu izlemeye (çöp toplamaya zorlama) yardımcı olması için bir düğme işleyicisine ekledim GC.Collect(2) .

mainWindowController.Window.StandardWindowButton (NSWindowButton.CloseButton).Activated += HandleActivated;

tarafından NSButton döndürülen StandardWindowButton() , bir olay kaydedilmiş olsa bile toplanıyordu (hata bu). Tıklayarak bu olayı çağırmaya çalışırken, düğme çöp olarak toplanmışsa kilitleniyoruz.

Bu sorunun kök nedeni bu olmasa da, bunun gibi yığın izlemelerine de ed Objective-Cişlevlerindeki [Export]yanlış yöntem imzaları neden olabilir. Örneğin, bir yöntem bir parametrenin bir olmasını out string bekliyorsa ve bunu olarak stringyazarsanız, aynı şekilde kilitlenebiliriz.

Örnek 3: Geri çağırmalar ve yönetilen nesneler

Birçok Cocoa API'sinde, bir olay gerçekleştiğinde kitaplık tarafından "geri çağrılır", size yanıt verme şansı verir veya bir görevi gerçekleştirmek için bazı verilere ihtiyaç duyulduğunda. Öncelikle Temsilci ve Veri Kaynağı desenlerini düşünseniz de, bu şekilde çalışan çok sayıda API vardır. Örneğin, bir NSView öğesinin yöntemlerini geçersiz kılıp görsel ağacına eklediğinizde, appkit'in belirli olaylar gerçekleştiğinde sizi geri çağırmasını beklersiniz.

Neredeyse tüm durumlarda, Xamarin.Mac bu geri çağırmaların yönetilen nesne hedefinin yine de geri çağrılabilirken Çöp Toplama olmasını doğru bir şekilde engeller. Ancak, bağlamadaki hatalar nadiren bunu kesintiye uğratabilir. Böyle bir durumda, şuna benzer hoş olmayan kilitlenmeler görebilirsiniz:

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread

0   libsystem_kernel.dylib              0x98c2f69a __pthread_kill + 10
1   libsystem_pthread.dylib             0x90341f19 pthread_kill + 101
2   libsystem_c.dylib                   0x9453feee abort + 156
3   libmonosgen-2.0.dylib               0x020bfba5 mono_handle_native_sigsegv + 757
4   libmonosgen-2.0.dylib               0x0210b812 mono_arch_handle_altstack_exception + 162
5   libmonosgen-2.0.dylib               0x0200c55e mono_sigsegv_signal_handler + 446
6   libsystem_platform.dylib            0x9513003b _sigtramp + 43
7   ???                                 0xffffffff 0 + 4294967295
8   libmonosgen-2.0.dylib               0x0200c3a0 mono_sigill_signal_handler + 48
9   com.apple.AppKit                    0x99f76041 -[NSView setFrame:] + 448
10  com.apple.AppKit                    0x9a1fd4ea -[NSToolbarView adjustToWindow:attachedToEdge:] + 198
11  com.apple.AppKit                    0x9a1fd414 -[NSToolbar _adjustViewToWindow] + 68
12  com.apple.AppKit                    0x9a01eb0d -[NSToolbar _windowWillShowToolbar] + 79

Bu kılavuz, kırpılırsa bu tür hataları izlemenize, düzeltilebilmeleri için doğru şekilde raporlamanıza ve o zamana kadar kodunuzda geçici olarak çalışmanıza yardımcı olur.

Bulma

Bu nitelikteki hatalarla hemen hemen her durumda, birincil belirti yerel kilitlenmeler, normalde benzer mono_sigsegv_signal_handlerbir şeyle veya _sigtrap yığının üst karelerindedir. Cocoa, C# kodunuz için geri çağırmaya, çöp toplama nesnesine vurmaya ve kilitlenmeye çalışır. Ancak, bu sembollerle her kilitlenmenin nedeni bunun gibi bir bağlama sorunu değildir, sorunun bu olduğunu onaylamak için bazı ek araştırma yapmanız gerekir.

Bu hataların izlenmesini zorlaştıran şey, bunların yalnızca bir çöp toplama söz konusu nesneden atıldıktan sonra oluşmasıdır. Bu hatalardan birine isabet ettiğinizi düşünüyorsanız, başlangıç dizinizde bir yere aşağıdaki kodu ekleyin:

new System.Threading.Thread (() =>
{
    while (true) {
         System.Threading.Thread.Sleep (1000);
         GC.Collect ();
    }
}).Start ();

Bu, uygulamanızı çöp toplayıcıyı her saniye çalıştırmaya zorlar. Uygulamanızı yeniden çalıştırın ve hatayı yeniden oluşturmaya çalışın. Hemen veya rastgele yerine tutarlı bir şekilde kilitlenirseniz doğru yoldasınızdır.

Raporlama

Sonraki adım, bağlamanın gelecekteki sürümlerde düzeltilmesi için sorunu Xamarin'e bildirmektir. İş veya kurumsal lisans sahibiyseniz adresinden bir bilet açın

visualstudio.microsoft.com/vs/support/

Aksi takdirde, mevcut bir sorunu arayın:

GitHub sorunlarının tümü geneldir. Açıklamaları veya ekleri gizlemek mümkün değildir.

Lütfen aşağıdakilerden mümkün olduğunca fazlasını ekleyin:

  • Sorunu yeniden üretmeye yönelik basit bir örnek. Bu mümkün olduğunda çok değerlidir.
  • Kilitlenmenin tam yığın izlemesi.
  • Kilitlenmeyi çevreleyen C# kodu.

Geçici çözüm

Sorunu izledikten sonra bağlama düzeltilene kadar geçici bir çözümle sorunu düzeltme eki uygulama basit olabilir. Amaç, hatalı şekilde atılan nesnenin (Görünüm, Temsilci, VeriKaynağı) açık bir başvuru tutarak bellekten ayrılmasını önlemektir.

Nesnenin yalnızca tek bir örneğinin bulunduğu basit durumlar için kodu şu şekilde değiştirin:

void AddObject ()
{
    item.View = new MyView ();
    ...
}

Bunun gibi bir statik değişken kullanmak için:

static NSObject view;
...

void AddObject ()
{
    view = new MyView ();
    item.View = view;
    ...
}

Birden çok örneğin oluşturulabileceği durumlarda statik HashSet kullanılabilir:

static HashSet<NSObject> collection = new HashSet<NSObject> ();
...

void AddObject ()
{
    item.View = new MyView ();
    collection.Add (item.View );
    ...
}

Bağlama düzeltildikten ve düzeltmeyi içeren Xamarin.Mac sürümüne yükseltildikten sonra geçici kod kaldırılabilir.

Özel durum kabarcıkları ve Objective-C

C# Özel Durumunun, yönetilen kodu çağırma Objective-C yöntemine "kaçış" yapması için hiçbir zaman izin vermemelisiniz. Bunu yaparsanız, sonuçlar tanımlanmamıştır, ancak genellikle kilitlenmeyi içerir. Genel olarak, sorunlarınızı hızlı bir şekilde çözmenize yardımcı olmak üzere hem yerel hem de yönetilen kilitlenmeler için yararlı bilgileri kabartmak için yapabileceğimiz her şeyi yaparız.

Teknik nedenlerle fazla takılmadan, her yönetilen/yerel sınırda yönetilen özel durumları yakalamak için altyapıyı ayarlamak önemsiz derecede pahalı değildir ve birçok yaygın işlemde gerçekleşen birçok geçiş vardır. Özellikle kullanıcı arabirimi iş parçacığını içeren işlemlerin çoğu hızlı bir şekilde bitmelidir, aksi takdirde uygulamanız kekelenir ve kabul edilemez performans özelliklerine sahip olur. Bu geri çağırmaların çoğu, nadiren atma olasılığı olan çok basit şeyler yapar, bu nedenle bu ek yük bu durumlarda hem pahalı hem de gereksiz olacaktır.

Bu nedenle, bu denemeleri / yakalamaları sizin için ayarlamayız. Kodladığınız yerlerin önemsiz olmayan şeyler yaptığı yerler için (boolean veya basit matematik döndürmenin ötesinde), kendinizi yakalamayı deneyebilirsiniz.