Как да разбера кой притежава работна нишка, която все още работи, когато приложението ми излезе?

Не след дълго надграждане до VS2010, моето приложение не се изключва правилно. Ако затворя приложението и след това натисна пауза в IDE, виждам това:

алтернативен текст

Проблемът е, че няма контекст. Стекът за повиквания просто казва [Външен код], което не е много полезно.

Ето какво направих досега, за да се опитам да стесня проблема:

  • изтри всички външни добавки, за да минимизира броя на стартираните работни нишки
  • задавам точки на прекъсване в моя код навсякъде, където създавам работни нишки (и делегати + BeginInvoke, тъй като мисля, че така или иначе са означени като "Работна нишка" в програмата за отстраняване на грешки). Никой не беше ударен.
  • задайте IsBackground = true за всички нишки

Докато мога да направя следващата груба стъпка, която е да върна кода си обратно до точка, където това не се е случило и след това да прегледам всички регистрационни файлове за промени, това не е много ефективно. Може ли някой да препоръча по-добър начин да разберете това, предвид забележителната липса на информация, представена от дебъгера?

Единствените други неща, за които се сещам, включват:

  • прочетете на WinDbg и се опитайте да го използвате, за да спрете всеки път, когато се стартира нишка. Поне си мислех, че е възможно... :)
  • коментирайте огромни блокове код, докато приложението се затвори правилно, след което започнете да премахвате коментарите, докато не се затвори.

АКТУАЛИЗАЦИЯ

Може би тази информация ще бъде от полза. Реших да използвам WinDbg и да го прикача към приложението си. След това го затворих и превключих на нишка 0 и изхвърлих съдържанието на стека. Ето какво имам:

ThreadCount:      6
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       4
Hosted Runtime:   no
                                   PreEmptive   GC Alloc                Lock
       ID  OSID ThreadOBJ    State GC           Context       Domain   Count APT Exception
   0    1  1c70 005a65c8      6020 Enabled  02dac6e0:02dad7f8 005a03c0     0 STA
   2    2  1b20 005b1980      b220 Enabled  00000000:00000000 005a03c0     0 MTA (Finalizer)
XXXX    3       08504048     19820 Enabled  00000000:00000000 005a03c0     0 Ukn
XXXX    4       08504540     19820 Enabled  00000000:00000000 005a03c0     0 Ukn
XXXX    5       08516a90     19820 Enabled  00000000:00000000 005a03c0     0 Ukn
XXXX    6       08517260     19820 Enabled  00000000:00000000 005a03c0     0 Ukn
0:008> ~0s
eax=c0674960 ebx=00000000 ecx=00000000 edx=00000000 esi=0040f320 edi=005a65c8
eip=76c37e47 esp=0040f23c ebp=0040f258 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
USER32!NtUserGetMessage+0x15:
76c37e47 83c404          add     esp,4
0:000> !clrstack
OS Thread Id: 0x1c70 (0)
Child SP IP       Call Site
0040f274 76c37e47 [InlinedCallFrame: 0040f274] 
0040f270 6baa8976 DomainBoundILStubClass.IL_STUB_PInvoke(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32)*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\WindowsBase\d17606e813f01376bd0def23726ecc62\WindowsBase.ni.dll

0040f274 6ba924c5 [InlinedCallFrame: 0040f274] MS.Win32.UnsafeNativeMethods.IntGetMessageW(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32)
0040f2c4 6ba924c5 MS.Win32.UnsafeNativeMethods.GetMessageW(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32)
0040f2dc 6ba8e5f8 System.Windows.Threading.Dispatcher.GetMessage(System.Windows.Interop.MSG ByRef, IntPtr, Int32, Int32)
0040f318 6ba8d579 System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
0040f368 6ba8d2a1 System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
0040f374 6ba7fba0 System.Windows.Threading.Dispatcher.Run()
0040f380 62e6ccbb System.Windows.Application.RunDispatcher(System.Object)*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\PresentationFramewo#\7f91eecda3ff7ce478146b6458580c98\PresentationFramework.ni.dll

0040f38c 62e6c8ff System.Windows.Application.RunInternal(System.Windows.Window)
0040f3b0 62e6c682 System.Windows.Application.Run(System.Windows.Window)
0040f3c0 62e6c30b System.Windows.Application.Run()
0040f3cc 001f00bc MyApplication.App.Main() [C:\code\trunk\MyApplication\obj\Debug\GeneratedInternalTypeHelper.g.cs @ 24]
0040f608 66c421db [GCFrame: 0040f608]

РЕДАКТИРАНЕ -- не съм сигурен дали това помага, но стекът за извикване на основната нишка изглежда така:

    [Managed to Native Transition]  
>   WindowsBase.dll!MS.Win32.UnsafeNativeMethods.GetMessageW(ref System.Windows.Interop.MSG msg, System.Runtime.InteropServices.HandleRef hWnd, int uMsgFilterMin, int uMsgFilterMax) + 0x15 bytes  
    WindowsBase.dll!System.Windows.Threading.Dispatcher.GetMessage(ref System.Windows.Interop.MSG msg, System.IntPtr hwnd, int minMessage, int maxMessage) + 0x48 bytes 
    WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame = {System.Windows.Threading.DispatcherFrame}) + 0x85 bytes 
    WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x49 bytes  
    WindowsBase.dll!System.Windows.Threading.Dispatcher.Run() + 0x4c bytes  
    PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x17 bytes  
    PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x6f bytes 
    PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x26 bytes 
    PresentationFramework.dll!System.Windows.Application.Run() + 0x1b bytes 

Направих търсене в него и открих, че някои публикации, свързани с WPF GUI, висят и може би това ще ми даде още улики.


person Dave    schedule 21.12.2010    source източник
comment
Какво прави основната ви нишка?   -  person Tim Lloyd    schedule 21.12.2010
comment
Просто седя там и чакам някой да щракне върху бутон.   -  person Dave    schedule 21.12.2010
comment
@Dave имам предвид какво прави, когато сте затворили приложението? Дали изпълнява някакъв близък манипулатор на събития и чака нещо, което никога няма да се случи?   -  person Tim Lloyd    schedule 21.12.2010
comment
това не мога да разбера. Всъщност се обърках по-рано. Когато се опитам да изтегля стека на повикванията за основната нишка, получавам [External code]. Когато го правя за работната нишка, стекът за повиквания е напълно празен.   -  person Dave    schedule 21.12.2010
comment
@Dave И какво изхвърляне получавате, когато превключите към нишка 1, т.е. вашата основна нишка?   -  person Tim Lloyd    schedule 21.12.2010
comment
@chibacity: отне ми време да се върна към това. Няма нишка 1 (вижте по-горе). Изглежда, че 0 трябва да е основната нишка...   -  person Dave    schedule 22.12.2010
comment
@Dave, бих се съсредоточил върху създаването на малко приложение, за да опитам да възпроизведа това. По-специално бих обърнал внимание на кода около това как се показва основният прозорец и как потребителят взаимодейства с приложението, за да го затвори, и кода, който се свързва с това, т.е. манипулатори на събития и т.н. Създаване на скелет на стартиране и изключване код, който симулира вашето приложение, би бил добро начало.   -  person Tim Lloyd    schedule 23.12.2010
comment
@Dave Проследяването на стека на основната ви нишка показва, че основната ви нишка все още изпълнява цикъл на съобщения и не работи в очакване на съобщение, т.е. GetMessageW. В действителност затварянето на прозореца трябва да доведе до получаване на съобщение WM_QUIT, цикълът на съобщенията да го прочете, след това да излезе и основната нишка да се отвие, т.е. нормално излизане от приложението. По някаква причина не е получил WM_QUIT.   -  person Tim Lloyd    schedule 23.12.2010
comment
@Dave Или не е получило съобщение WM_CLOSE за главния прозорец.   -  person Tim Lloyd    schedule 23.12.2010
comment
@chibacity Добре, ще го разгледам. Приложението е доста голямо, но ще се опитам да го стесня. Не видях този проблем, докато не конвертирах всичко в .NET 4.0 за VS2010... може да е свързано с нещо, което направих по време на преобразуването.   -  person Dave    schedule 23.12.2010
comment
@Dave Първо изпробвайте програмата WM_CLOSE в моята актуализация на отговора - тогава поне ще знаем дали основната нишка наистина виси.   -  person Tim Lloyd    schedule 23.12.2010
comment
@chibacity не съм сигурен дали мога да използвам Winspector на WPF приложения, но създадох манипулатор за събитието Closed на моя основен GUI и той се обработва. Предполагам, че това означава, че съобщението WM_CLOSE е получено.   -  person Dave    schedule 24.12.2010
comment
@Dave Моля, изпробвайте примерното приложение, което предоставих - това ще разбере дали основната ви нишка е увиснала или просто е неактивна в очакване на съобщения.   -  person Tim Lloyd    schedule 24.12.2010


Отговори (5)


Идентификационният номер на работната нишка, който виждате, е 0. Това е нишка от рамка и се очаква - това не е нишка, която програмата е "създала". Ако се прикачите към който и да е .Net процес, ще видите това. Не съм сигурен коя нишка на рамката е - определено не е нишката на финализатора, тъй като това никога не е нишка 0. Може би JIT нишка?

Това, което е по-интересно тогава, е вашата основна нишка, тъй като изглежда, че тя виси. Бих се съсредоточил върху отстраняването на грешки в основната ви нишка, за да стигна до дъното на това. Заключил ли се е от манипулатор на събития със затворен прозорец в очакване на нещо, което никога няма да се случи например?

Актуализация

След прочитане на проследяването на стека за основната нишка, добавена към въпроса, би било интересно да се проведе тест, за да се установи дали основната нишка е спряла или просто е неактивна в очакване на съобщение (и че чака WM_CLOSE че никога не е получавано или никога не е изпращано).

Кодът по-долу може да се използва за ръчно изпращане на съобщение WM_CLOSE до вашето приложение. Просто изчакайте програмата ви да увисне, когато я затворите, и след това стартирайте кода. Заменете името на процеса с вашето собствено.

Актуализация 2

Добре, изглежда, че основната нишка е добре и наистина е увиснала, тъй като не обработва WM_CLOSE или WM_QUIT съобщения.

Моля, опитайте да направите най-малкото приложение, което може да възпроизведе проблема и да публикувате кода.

Примерно приложение WM_CLOSE\WM_QUIT

internal class Program
{
    private const int WM_QUIT = 0x0012;
    private const int WM_CLOSE = 0x0010;

    [DllImport("user32.dll")]
    private static extern bool PostMessage(int hhwnd, uint msg, IntPtr wParam, IntPtr lParam);

    private static void Main()
    {
        Process p = GetProcess("Your process name - no '.exe' required");

        CloseMainWindow(p);
    }

    private static Process GetProcess(string name)
    {
        List<Process> processes = Process.GetProcessesByName(name).ToList();

        if (processes.Count != 1)
        {
            throw new Exception(
              "Expected 1 process with name '" + name +
              "' but found " + processes.Count + ".");
        }

        return processes[0];
    }

    private static void CloseMainWindow(Process p)
    {
        PostMessage(p, WM_CLOSE, "close");
    }

    private static void QuitApplication(Process p)
    {
        PostMessage(p, WM_QUIT, "quit");
    }

    private static void PostMessage(Process p, uint message, string name)
    {
        Console.WriteLine("Posting {0} message to '{1}'...", name, p.ProcessName);

        bool succeeded = PostMessage(p.MainWindowHandle.ToInt32(), message, IntPtr.Zero, IntPtr.Zero);

        Console.WriteLine("Posted {0} message to '{1}' (succeeded:{2}).", name, p.ProcessName, succeeded);
    }
} 
person Tim Lloyd    schedule 21.12.2010
comment
@chibacity: Разбирам какво казваш -- управляваният идентификатор е 0 и това е, което Thread.CurrentThread.GetHashCode() връща, нали? Хм... Ще разгледам това, което предложихте, благодаря. - person Dave; 21.12.2010
comment
@Dave Идентификаторът на управляваната нишка е нещо, което се управлява от рамката и може да бъде извлечено чрез Thread.CurrentThread.ManagedThreadId. Той е различен от родния идентификатор, който се управлява от операционната система. Нишката с управляван идентификатор 0 е нишка на рамка, а не нишка, която сте създали или използвате директно. Това е първата нишка, която се управлява от рамката, следователно има идентификатор 0. Идва преди основната нишка, която има идентификатор 1, и преди нишката на финализатора, която е 2. - person Tim Lloyd; 21.12.2010
comment
@chibacity Има ли смисъл ID на управляваната нишка 0 да идва и да си отива? Само за да тествам нещата, поставих на пауза изпълнението на приложението и проверих нишките, които се изпълняват. Бих отварял различни диалогови прозорци, поставял на пауза, проверявал нишки, премахвал паузата. Понякога ID на управляваната нишка 0 е там, понякога изчезва, а понякога се връща. Това означава ли нещо? Опитвам се да намеря онлайн ресурси, които ще обяснят значението на нишка 0. - person Dave; 23.12.2010
comment
@Dave Не знам кървавите подробности за това как се държат нишките на рамката в детайли. Има и оскъдна информация за това онлайн - тя е много вътрешна, така че не е широко публикувана\документирана. Все още мисля, че имате проблем с основната си нишка и лаете по грешното дърво, гледайки нишка 0. Не мога наистина да помогна без изходния код! Не можете ли да направите малка възпроизводима проба? Ако не можете, това е още по-силно доказателство, че това е вашата основна нишка, т.е. специфична за програмата. - person Tim Lloyd; 23.12.2010
comment
@chibacity благодаря -- съжалявам, обърках се и се чудех къде е актуализацията на вашия отговор. :) Колко съм глупав. Ще разгледам това. - person Dave; 24.12.2010
comment
@chibacity Получавам PInvokeStackImbalance -- Извикване на функцията на PInvoke 'HungGUITest!HungGUITest.Program::PostMessage' е разбалансирало стека. Това вероятно е, защото подписът на управлявания PInvoke не съответства на подписа на неуправляваната цел. Проверете дали конвенцията за извикване и параметрите на подписа на PInvoke съответстват на целевия неуправляван подпис. - person Dave; 24.12.2010
comment
@Dave Опа, моя грешка, подписът за PostMessage беше по памет. Добавих два липсващи (но неизползвани в този случай) параметъра към подписа. Пропуснах го, тъй като получавате небалансираната грешка само когато го стартирате с прикачения дебъгер! Моля, използвайте актуализиран код. :) - person Tim Lloyd; 24.12.2010
comment
@chibacity няма проблем, трябваше да го разгледам по-подробно, но нямам много опит с PInvoke. Стартирах вашето приложение и то публикува съобщението, но моето приложение все още работи. - person Dave; 24.12.2010
comment
@Dave Опитайте да стартирате приложението, но извикайте QuitApplication, вместо CloseMainWindow. - person Tim Lloyd; 24.12.2010
comment
@Dave Моля, уверете се, че вашето приложение няма прикачен дебъгер и не сте спрели неговите нишки. - person Tim Lloyd; 24.12.2010
comment
@chibacity добре, излязох извън дебъгера и отворих диспечера на задачите, за да мога да проверя процеса. Стартирах приложението ви с помощта на CloseMainWindow и QuitApplication и то все още работи. - person Dave; 24.12.2010
comment
@Dave Основната нишка определено виси тогава, тъй като не е обработила нито едно от съобщенията. Опитайте да изпратите някое от двете съобщения до друго приложение, ще го видите да излиза, например бележник. - person Tim Lloyd; 24.12.2010
comment
@chibacity благодаря много за помощта. Предполагам, че ще трябва да прибягна до грубо отстраняване на грешки. - person Dave; 24.12.2010
comment
@Dave може би имате извикване на Invoke или BeginInvoke, което спира основната нишка. Един от начините за достигане на това ниво би бил да напишете IDispatcher прокси и да използвате проксито за всичките си диспечерски повиквания. Просто препратете обажданията към диспечера на приложението. След като имате прокси на място, можете да обвиете своите действия и да добавите преди\след влизане. Трябва да можете да видите къде е вашето зависване, тъй като ще има запис преди, но не и след запис. Обмислете използването на брояч и условна точка на прекъсване, за да го заковате, след като разберете какъв е моделът. - person Tim Lloyd; 24.12.2010
comment
@chibacity страхотно предложение, никога не бих се сетил за това. Благодаря! Ще започна да работя върху това. - person Dave; 24.12.2010
comment
@chibacity В крайна сметка взех по-простия маршрут и зададох точки на прекъсване на всички повиквания към BeginInvoke. Нито един от тях не беше извикан в режим на повреда, който се опитвам да отстраня. - person Dave; 28.12.2010
comment

Намерихте отговора в Как да оформите Views в RelativeLayout програмно?

Трябва изрично да зададем id с помощта на setId(). Само тогава RIGHT_OF правила имат смисъл.

Друга грешка, която направих, е повторното използване на обекта layoutparams между контролите. Трябва да създадем нов обект за всяка контрола

- person Tim Lloyd; 28.12.2010
comment
@chibacity Да -- сега ще опитам подхода с груба сила -- връщам кода си назад, докато проблемът изчезне, и след това разбера какви промени направих в проекта, които го накараха да се счупи. С малко късмет такава точка за връщане съществува, но също така е възможно, когато преобразувах проекта във VS2010, проблемът вече да е бил там. - person Dave; 28.12.2010
comment
@chibacity - вижте отговора ми тук -- разбрах проблема. Предполагам, че не съм разбрал как MEF инстанцира обекти. Ще ти дам наградата, но трябва да изчакам 20 часа. Много благодаря, че ми помогнахте да науча някои нови неща с .NET в процеса. - person Dave; 28.12.2010
comment
@Dave Радвам се да видя, че стигна до дъното - радвам се, че помогнах. Наздраве за наградата. :) - person Tim Lloyd; 29.12.2010
comment
@chibacity няма проблем. Смешното е, че имам същия проблем в един клон и когато го закърпих, проблемът не изчезна там. Но тъй като работи в багажника, това е основното в момента. :) - person Dave; 29.12.2010

Добавете следния манипулатор към всеки прозорец, който вашето приложение създава в отделна нишка:

win.Closed += (o, e) => win.Dispatcher.InvokeShutdown();

Ако главната нишка увисне, извикайте win.Dispatcher.InvokeShutdown() в MainWindow.Closed - това автоматично ще затвори всички други прозорци, създадени в главната нишка.

Без този манипулатор моето приложение със следния код също увисна при излизане:

void Worker() {
    var win = new Window();
    // win.Closed += onWindowClose ?? ((o, e) => editor.Dispatcher.InvokeShutdown());
    editor.Show();
    System.Windows.Threading.Dispatcher.Run();
}
person Mykola Bogdiuk    schedule 28.12.2010
comment
благодаря за предложението, ще добавя това като превантивна мярка, но приложението ми има само главния прозорец. Но никога не се знае... Ще ви уведомя как е. :) - person Dave; 28.12.2010
comment
+1, Това поправи свързан досаден проблем, който имах. Компонент на трета страна спираше приложението при изключване (мисля, че трябва да е създавало някакъв скрит прозорец) и извикването на това от главния прозорец Затворено събитие го поправи. - person Dan Bryant; 04.10.2012

Най-накрая разбрах този проблем. Имах контрола, която беше Imported от MEF, но всъщност никога не беше извикана (все още). Мисля, че MEF го създаде, въпреки че не беше споменато никъде (предполагах, че създаването не е станало докато ресурсът не бъде поискан, но очевидно съм грешал). Поправих проблема, като използвах Lazy‹> инстанциране и сега работи. Това наистина ме хвърли, но благодаря на всички за помощта. Научих много, опитвайки се да коригирам този проблем.

person Dave    schedule 28.12.2010

Ако създавате работни нишки (вместо нишки на пул), задайте техните Име на нещо описателно при създаването.

person Mitch Wheat    schedule 21.12.2010
comment
Ако наистина сте в задръстване, можете да преименувате нишките на пула, като използвате отражение. Ако сте отчаяни от разбира се... - person Tim Lloyd; 21.12.2010
comment
това е добро предложение за в бъдеще, но аз буквално зададох точки на прекъсване на всяко място, където създадох нишка (така че ако го пропусна там, ще пропусна и Името :) ). Има ли друго място, където мога да задам точка на прекъсване, когато се създава нишка, като настройка на IDE? - person Dave; 21.12.2010
comment
@Dave, когато използвате конструкции като BeginInvoke, всъщност не създавате нишка. Работен елемент е поставен на опашка към threadpoool и съществуваща нишка го взима. - person Tim Lloyd; 21.12.2010
comment
добре благодаря. Знаех, че BeginInvoke използва ThreadPool, но мислех, че те все още се показват в прозореца Threads като WorkerThread. Предполагам, че не? Ще трябва да погледна... Знам, че са някъде там, защото съм ги виждал, когато преминавах през моя код. - person Dave; 21.12.2010
comment
@Mitch благодаря за предложението. Въпреки че не решава проблема ми, наистина ми харесва да гледам в прозореца Threads и сега виждам нещо по-смислено от просто Worker Thread. :) Много по-добре! - person Dave; 21.12.2010
comment
@Dave Да, нишките на пула от нишки ще се появят в прозореца на нишките. Имах предвид въпроса ви Има ли друго място, където мога да задам точка на прекъсване, когато се създава нишка. Това няма да хване използването на нишки на пула от нишки, тъй като вие всъщност не ги създавате - това е всичко. - person Tim Lloyd; 21.12.2010
comment
@chibacity Ах, разбирам какво искаш да кажеш. Благодаря за пояснението. Скоро ще разгледа другия ви коментар. - person Dave; 21.12.2010

Добавете регистриране към целия си код, за да регистрирате метода и ID на нишката. Можете да използвате замяна на регулярен израз, за ​​да го поставите в началото на всеки отделен метод. След това стартирайте приложението и го изключете. Вижте кои регистрационни съобщения имат същия идентификатор на нишка като работната нишка, която не се изключва.

person Samuel Neff    schedule 21.12.2010
comment
ID на работната нишка е 0 - това е нишка на рамка, въпреки че не съм сигурен каква е нейната роля. Предполагам, че всъщност е проблем с основната нишка. - person Tim Lloyd; 21.12.2010
comment
Благодаря. Мисля, че това е най-добрият съвет досега. Вече регистрирам цялата тази информация с log4net и дори не ми беше хрумнало да проследя поведението на нишката обратно през регистрационните файлове, за да получа контекст по този начин! хубаво. :) - person Dave; 21.12.2010
comment
Интересно, логовете никъде не показват 3256, но 6524 беше там. Изпълнението на приложението изглежда напълно нормално в този момент и функционира добре, докато не изключа. Ще опитам да променя конфигурацията на log4net, за да регистрира всички входни точки и ще видим дали ще се появи нещо. - person Dave; 21.12.2010
comment
Идентификаторът на управляваната нишка е 0, а не собственият идентификатор. Това е първата нишка, която се управлява от рамката, следователно има идентификатор 0. Идва преди основната нишка, която има идентификатор 1, и преди нишката на финализатора, която е 2. Идентификаторът на управляваната нишка се управлява от рамка и е различен от родния идентификатор, към който се позовавате и който се управлява от операционната система. Тъй като това е специална рамка, тя не е създадена от програмата като такава. Той е стартиран преди програмата (т.е. преди Main) и се използва от рамката. Всички .Net програми имат тази тема. - person Tim Lloyd; 21.12.2010