návod: ladění paralelní aplikace v Visual Studio (C#, Visual Basic, C++)
Tento návod ukazuje, jak použít Paralelní úlohy a okna paralelních zásobníků k ladění paralelní aplikace. Tato okna vám pomůžou pochopit a ověřit chování kódu, který používá Task Parallel Library (TPL) nebo Concurrency Runtime. Tento návod obsahuje vzorový kód, který obsahuje předdefinované zarážky. Po zalomení kódu návod ukazuje, jak použít Paralelní úlohy a okna paralelní zásobníky k její kontrole.
Tento návod učí tyto úlohy:
Jak zobrazit zásobníky volání všech vláken v jednom zobrazení.
Jak zobrazit seznam
System.Threading.Tasks.Taskinstancí, které jsou vytvořeny ve vaší aplikaci.Jak zobrazit skutečné zásobníky volání úkolů místo vláken.
Jak přejít na kód z okna Paralelní úlohy a paralelní zásobníky
Způsob, jakým se Windows vypořádat se škálováním prostřednictvím seskupení, lupy a dalších souvisejících funkcí.
Požadavky
Tento návod předpokládá, že je povolený pouze můj kód (ve výchozím nastavení je povolený v novějších verzích Visual Studio). V nabídce nástroje klikněte na položku Možnosti, rozbalte uzel ladění , vyberte možnost Obecné a poté vyberte možnost Povolit pouze můj kód (pouze spravované). Pokud tuto funkci nenastavíte, můžete i nadále používat tento návod, ale výsledky se mohou lišit od ilustrací.
Ukázka jazyka C#
Použijete-li ukázku jazyka C#, tento návod také předpokládá, že je externí kód skrytý. Chcete-li přepnout, zda je zobrazen externí kód, klikněte pravým tlačítkem myši na záhlaví tabulky v okně zásobník volání a potom vyberte nebo zrušte zaškrtnutí Zobrazit externí kód. Pokud tuto funkci nenastavíte, můžete i nadále používat tento návod, ale výsledky se mohou lišit od ilustrací.
Ukázka C++
Použijete-li ukázku jazyka C++, můžete ignorovat odkazy na externí kód v tomto tématu. Externí kód se vztahuje pouze na ukázku C#.
Ilustrace
Ilustrace v tomto tématu zaznamenané na čtyřjádrovém počítači, na kterém je spuštěná ukázka jazyka C# I když můžete použít jiné konfigurace k dokončení tohoto Názorného postupu, ilustrace se mohou lišit od toho, co se zobrazuje v počítači.
Vytvoření ukázkové Project
Vzorový kód v tomto návodu je určen pro aplikaci, která nic nedělá. Cílem je pouze pochopit, jak používat okna nástrojů k ladění paralelní aplikace.
Vytvoření ukázkového projektu
otevřete Visual Studio a vytvořte nový projekt.
Pokud okno Start není otevřeno, klikněte na tlačítko > Start okna.
V okně Start vyberte možnost vytvořit nový projekt.
V okně vytvořit nový projekt zadejte do vyhledávacího pole nebo zadejte Console . dále v seznamu jazyk vyberte C#, C++ nebo Visual Basic a v seznamu platforma zvolte Windows .
Po použití filtrů jazyků a platforem zvolte konzolovou aplikaci pro .NET Core nebo C++ a pak zvolte Další.
Poznámka
pokud nevidíte správnou šablonu, přejděte do části nástroje > získat nástroje a funkce..., které otevře Instalační program pro Visual Studio. Zvolte možnost vývoj pro vývoj a vývoj pro různé platformy .NET Core s využitím úlohy C++ a pak zvolte změnit.
v okně konfigurovat nový projekt zadejte název nebo použijte výchozí název v poli Project název . Pak klikněte na tlačítko Další nebo vytvořit, podle toho, jaká možnost je k dispozici.
V případě .NET Core zvolte Doporučené cílové rozhraní nebo .NET 6 a pak zvolte vytvořit.
v horním řádku nabídek vyberte soubor > nový > Project. V levém podokně dialogového okna Nový projekt vyberte následující:
- v případě aplikace v jazyce c# vyberte v části Visual C# možnost Windows plocha a potom v prostředním podokně zvolte konzolová aplikace (.NET Framework).
- v případě aplikace Visual Basic vyberte v části Visual Basic možnost Windows plocha a potom v prostředním podokně zvolte konzolová aplikace (.NET Framework).
- v aplikaci C++ v části Visual C++ zvolte Windows plocha, a pak zvolte Windows konzolová aplikace.
pokud se nezobrazuje konzolová aplikace (.net Core) nebo, pro C++, šablona projektu konzolová aplikace , přejděte do části nástroje > získat nástroje a funkce..., které otevře Instalační program pro Visual Studio. Zvolte možnost vývoj desktopových aplikací pro .NET nebo desktopové vývojové prostředí pomocí C++ a pak zvolte Upravit.
Pak zadejte název nebo použijte výchozí název a klikněte na OK.
Vyberte OK.
Zobrazí se nový projekt konzoly. Po vytvoření projektu se zobrazí zdrojový soubor.
Otevřete v projektu soubor kódu. cpp,. cs nebo. vb. Odstraňte jeho obsah a vytvořte prázdný soubor kódu.
Vložte následující kód pro zvolený jazyk do prázdného souboru kódu.
using System; using System.Threading; using System.Threading.Tasks; using System.Diagnostics; class S { static void Main() { pcount = Environment.ProcessorCount; Console.WriteLine("Proc count = " + pcount); ThreadPool.SetMinThreads(4, -1); ThreadPool.SetMaxThreads(4, -1); t1 = new Task(A, 1); t2 = new Task(A, 2); t3 = new Task(A, 3); t4 = new Task(A, 4); Console.WriteLine("Starting t1 " + t1.Id.ToString()); t1.Start(); Console.WriteLine("Starting t2 " + t2.Id.ToString()); t2.Start(); Console.WriteLine("Starting t3 " + t3.Id.ToString()); t3.Start(); Console.WriteLine("Starting t4 " + t4.Id.ToString()); t4.Start(); Console.ReadLine(); } static void A(object o) { B(o); } static void B(object o) { C(o); } static void C(object o) { int temp = (int)o; Interlocked.Increment(ref aa); while (aa < 4) { ; } if (temp == 1) { // BP1 - all tasks in C Debugger.Break(); waitFor1 = false; } else { while (waitFor1) { ; } } switch (temp) { case 1: D(o); break; case 2: F(o); break; case 3: case 4: I(o); break; default: Debug.Assert(false, "fool"); break; } } static void D(object o) { E(o); } static void E(object o) { // break here at the same time as H and K while (bb < 2) { ; } //BP2 - 1 in E, 2 in H, 3 in J, 4 in K Debugger.Break(); Interlocked.Increment(ref bb); //after L(o); } static void F(object o) { G(o); } static void G(object o) { H(o); } static void H(object o) { // break here at the same time as E and K Interlocked.Increment(ref bb); Monitor.Enter(mylock); while (bb < 3) { ; } Monitor.Exit(mylock); //after L(o); } static void I(object o) { J(o); } static void J(object o) { int temp2 = (int)o; switch (temp2) { case 3: t4.Wait(); break; case 4: K(o); break; default: Debug.Assert(false, "fool2"); break; } } static void K(object o) { // break here at the same time as E and H Interlocked.Increment(ref bb); Monitor.Enter(mylock); while (bb < 3) { ; } Monitor.Exit(mylock); //after L(o); } static void L(object oo) { int temp3 = (int)oo; switch (temp3) { case 1: M(oo); break; case 2: N(oo); break; case 4: O(oo); break; default: Debug.Assert(false, "fool3"); break; } } static void M(object o) { // breaks here at the same time as N and Q Interlocked.Increment(ref cc); while (cc < 3) { ; } //BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q Debugger.Break(); Interlocked.Increment(ref cc); while (true) Thread.Sleep(500); // for ever } static void N(object o) { // breaks here at the same time as M and Q Interlocked.Increment(ref cc); while (cc < 4) { ; } R(o); } static void O(object o) { Task t5 = Task.Factory.StartNew(P, TaskCreationOptions.AttachedToParent); t5.Wait(); R(o); } static void P() { Console.WriteLine("t5 runs " + Task.CurrentId.ToString()); Q(); } static void Q() { // breaks here at the same time as N and M Interlocked.Increment(ref cc); while (cc < 4) { ; } // task 5 dies here freeing task 4 (its parent) Console.WriteLine("t5 dies " + Task.CurrentId.ToString()); waitFor5 = false; } static void R(object o) { if ((int)o == 2) { //wait for task5 to die while (waitFor5) { ;} int i; //spin up all procs for (i = 0; i < pcount - 4; i++) { Task t = Task.Factory.StartNew(() => { while (true);}); Console.WriteLine("Started task " + t.Id.ToString()); } Task.Factory.StartNew(T, i + 1 + 5, TaskCreationOptions.AttachedToParent); //scheduled Task.Factory.StartNew(T, i + 2 + 5, TaskCreationOptions.AttachedToParent); //scheduled Task.Factory.StartNew(T, i + 3 + 5, TaskCreationOptions.AttachedToParent); //scheduled Task.Factory.StartNew(T, i + 4 + 5, TaskCreationOptions.AttachedToParent); //scheduled Task.Factory.StartNew(T, (i + 5 + 5).ToString(), TaskCreationOptions.AttachedToParent); //scheduled //BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died Debugger.Break(); } else { Debug.Assert((int)o == 4); t3.Wait(); } } static void T(object o) { Console.WriteLine("Scheduled run " + Task.CurrentId.ToString()); } static Task t1, t2, t3, t4; static int aa = 0; static int bb = 0; static int cc = 0; static bool waitFor1 = true; static bool waitFor5 = true; static int pcount; static S mylock = new S(); }#include "stdafx.h" #include <windows.h> #include <iostream> #include <ppl.h> #include <agents.h> #include <stdio.h> #include <concrtrm.h> #include <vector> CRITICAL_SECTION cs; using namespace ::std; using namespace ::std::tr1; using namespace ::Concurrency; task_group task4; task_group task3; task_group task2; volatile long aa = 0; volatile long bb = 0; volatile long cc = 0; static bool waitFor1 = true; static bool waitFor5 = true; #pragma optimize("", off) void Spin() { for(int i=0;i<50*50000;++i); } #pragma optimize("",on) template<class Func> class RunFunc { Func& m_Func; int m_o; public: RunFunc(Func func,int o):m_Func(func),m_o(o){ }; void operator()()const{ m_Func(m_o); }; }; void T(int o) { cout << "Scheduled run \n"; }; void R(int o) { if (o == 2) { while (waitFor5) { ;} Spin(); //use up all processors but 4 by scheduling 4 non-terminating tasks. int numProcsToBurn = GetProcessorCount() - 4; int i; vector<call<int>*> tasks; for (i = 0; i < numProcsToBurn; i++) { tasks.push_back(new call<int>([](int i){while(true)Spin();})); asend(tasks[i],1); cout << "Started task \n"; } task_handle<RunFunc<decltype(T)>> t6(RunFunc<decltype(T)>(T,i + 1 + 5)); task_handle<RunFunc<decltype(T)>> t7(RunFunc<decltype(T)>(T,i + 2 + 5)); task_handle<RunFunc<decltype(T)>> t8(RunFunc<decltype(T)>(T,i + 3 + 5)); task_handle<RunFunc<decltype(T)>> t9(RunFunc<decltype(T)>(T,i + 4 + 5)); task_handle<RunFunc<decltype(T)>> t10(RunFunc<decltype(T)>(T,i + 5 + 5)); task2.run(t6); task2.run(t7); task2.run(t8); task2.run(t9); task2.run(t10); //BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died DebugBreak(); } else { if (o!=4) throw; task3.wait(); } }; void Q() { // breaks here at the same time as N and M InterlockedIncrement(& cc); while (cc < 4) { ; } // task 5 dies here freeing task 4 (its parent) cout << "t5 dies\n"; waitFor5 = false; }; void P() { cout << "t5 runs\n"; Q(); }; void O(int o) { task_group t5; t5.run(&P); t5.wait(); R(o); }; void N(int o) { // breaks here at the same time as M and Q InterlockedIncrement(&cc); while (cc < 4) { ; } R(o); }; void M(int o) { // breaks here at the same time as N and Q InterlockedIncrement(&cc); while (cc < 3) { ; } //BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q DebugBreak(); InterlockedIncrement(&cc); while (true) Sleep(500); // for ever }; void L(int oo) { int temp3 = oo; switch (temp3) { case 1: M(oo); break; case 2: N(oo); break; case 4: O(oo); break; default: throw; //fool3 break; } } void K(int o) { // break here at the same time as E and H InterlockedIncrement(&bb); EnterCriticalSection(&cs); while (bb < 3) { ; } LeaveCriticalSection(&cs); Spin(); //after L(o); } void J(int o) { int temp2 = o; switch (temp2) { case 3: task4.wait(); break; case 4: K(o); break; default: throw; //fool2 break; } } static void I(int o) { J(o); } static void H(int o) { // break here at the same time as E and K InterlockedIncrement(&bb); EnterCriticalSection(&cs); while (bb < 3) { ; } LeaveCriticalSection(&cs); Spin(); //after L(o); } static void G(int o) { H(o); } static void F(int o) { G(o); } static void E(int o) { // break here at the same time as H and K while (bb < 2) { ; } //BP2 - 1 in E, 2 in H, 3 in J, 4 in K Spin(); // for native case only DebugBreak(); InterlockedIncrement(&bb); //after L(o); } static void D(int o) { E(o); } static void C(int o) { int temp = o; InterlockedIncrement(&aa); while (aa < 4) { ; } if (temp == 1) { // BP1 - all tasks in C DebugBreak(); waitFor1 = false; } else { while (waitFor1) { ; } } switch (temp) { case 1: D(o); break; case 2: F(o); break; case 3: case 4: I(o); break; default: throw; //fool break; } } static void B(int o) { C(o); } void A(int o) { B(o); } int main() { InitializeCriticalSection(&cs); task_group tasks; task_handle<RunFunc<decltype(A)>> t1(RunFunc<decltype(A)>(A,1)); tasks.run(t1); task_handle<RunFunc<decltype(A)>> t2(RunFunc<decltype(A)>(A,2)); task2.run(t2); task_handle<RunFunc<decltype(A)>> t3(RunFunc<decltype(A)>(A,3)); task3.run(t3); task_handle<RunFunc<decltype(A)>> t4(RunFunc<decltype(A)>(A,4)); task4.run(t4); getchar(); return 1; }Imports System Imports System.Threading Imports System.Threading.Tasks Imports System.Diagnostics Module S Sub Main() pcount = Environment.ProcessorCount Console.WriteLine("Proc count = " + pcount.ToString()) ThreadPool.SetMinThreads(4, -1) ThreadPool.SetMaxThreads(4, -1) t1 = New Task(AddressOf A, 1) t2 = New Task(AddressOf A, 2) t3 = New Task(AddressOf A, 3) t4 = New Task(AddressOf A, 4) Console.WriteLine("Starting t1 " + t1.Id.ToString()) t1.Start() Console.WriteLine("Starting t2 " + t2.Id.ToString()) t2.Start() Console.WriteLine("Starting t3 " + t3.Id.ToString()) t3.Start() Console.WriteLine("Starting t4 " + t4.Id.ToString()) t4.Start() Console.ReadLine() End Sub Sub A(ByVal o As Object) B(o) End Sub Sub B(ByVal o As Object) C(o) End Sub Sub C(ByVal o As Object) Dim temp As Integer = o Interlocked.Increment(aa) While (aa < 4) End While If (temp = 1) Then ' BP1 - all tasks in C Debugger.Break() waitFor1 = False Else While (waitFor1) End While End If Select Case temp Case 1 D(o) Case 2 F(o) Case 3, 4 I(o) Case Else Debug.Assert(False, "fool") End Select End Sub Sub D(ByVal o As Object) E(o) End Sub Sub E(ByVal o As Object) ' break here at the same time as H and K While (bb < 2) End While 'BP2 - 1 in E, 2 in H, 3 in J, 4 in K Debugger.Break() Interlocked.Increment(bb) 'after L(o) End Sub Sub F(ByVal o As Object) G(o) End Sub Sub G(ByVal o As Object) H(o) End Sub Sub H(ByVal o As Object) ' break here at the same time as E and K Interlocked.Increment(bb) Monitor.Enter(mylock) While (bb < 3) End While Monitor.Exit(mylock) 'after L(o) End Sub Sub I(ByVal o As Object) J(o) End Sub Sub J(ByVal o As Object) Dim temp2 As Integer = o Select Case temp2 Case 3 t4.Wait() Case 4 K(o) Case Else Debug.Assert(False, "fool2") End Select End Sub Sub K(ByVal o As Object) ' break here at the same time as E and H Interlocked.Increment(bb) Monitor.Enter(mylock) While (bb < 3) End While Monitor.Exit(mylock) 'after L(o) End Sub Sub L(ByVal oo As Object) Dim temp3 As Integer = oo Select Case temp3 Case 1 M(oo) Case 2 N(oo) Case 4 O(oo) Case Else Debug.Assert(False, "fool3") End Select End Sub Sub M(ByVal o As Object) ' breaks here at the same time as N and Q Interlocked.Increment(cc) While (cc < 3) End While 'BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q Debugger.Break() Interlocked.Increment(cc) While (True) Thread.Sleep(500) ' for ever End While End Sub Sub N(ByVal o As Object) ' breaks here at the same time as M and Q Interlocked.Increment(cc) While (cc < 4) End While R(o) End Sub Sub O(ByVal o As Object) Dim t5 As Task = Task.Factory.StartNew(AddressOf P, TaskCreationOptions.AttachedToParent) t5.Wait() R(o) End Sub Sub P() Console.WriteLine("t5 runs " + Task.CurrentId.ToString()) Q() End Sub Sub Q() ' breaks here at the same time as N and M Interlocked.Increment(cc) While (cc < 4) End While ' task 5 dies here freeing task 4 (its parent) Console.WriteLine("t5 dies " + Task.CurrentId.ToString()) waitFor5 = False End Sub Sub R(ByVal o As Object) If (o = 2) Then ' wait for task5 to die While waitFor5 End While '//spin up all procs Dim i As Integer For i = 0 To pcount - 4 - 1 Dim t As Task = Task.Factory.StartNew(Sub() While True End While End Sub) Console.WriteLine("Started task " + t.Id.ToString()) Next Task.Factory.StartNew(AddressOf T, i + 1 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled Task.Factory.StartNew(AddressOf T, i + 2 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled Task.Factory.StartNew(AddressOf T, i + 3 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled Task.Factory.StartNew(AddressOf T, i + 4 + 5, TaskCreationOptions.AttachedToParent) ' //scheduled Task.Factory.StartNew(AddressOf T, (i + 5 + 5).ToString(), TaskCreationOptions.AttachedToParent) ' //scheduled '//BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died Debugger.Break() Else Debug.Assert(o = 4) t3.Wait() End If End Sub Sub T(ByVal o As Object) Console.WriteLine("Scheduled run " + Task.CurrentId.ToString()) End Sub Private t1, t2, t3, t4 As Task Private aa As Integer = 0 Private bb As Integer = 0 Private cc As Integer = 0 Private waitFor1 As Boolean = True Private waitFor5 As Boolean = True Private pcount As Integer Private mylock As New S2() End Module Public Class S2 End ClassV nabídce File (Soubor) klikněte na Save All (Uložit vše).
V nabídce sestavení klikněte na příkaz znovu sestavit řešení.
Všimněte si, že jsou k dispozici čtyři volání
Debugger.Break(DebugBreakv ukázce jazyka C++), proto není nutné vkládat zarážky; pouhá spuštění aplikace způsobí přerušení v ladicím programu až čtyřikrát.
Použití okna Paralelní zásobníky: zobrazení vláken
V nabídce Ladit klikněte na Spustit ladění. Počkejte, než se dorazí na první zarážku.
Zobrazení zásobníku volání jednoho vlákna
v nabídce ladění přejděte na Windows a pak klikněte na vlákna. Ukotvěte okno vlákna v dolní části Visual Studio.
v nabídce ladění přejděte na Windows a pak klikněte na zásobník volání. Ukotvěte okno zásobník volání na spodní Visual Studio.
Dvakrát klikněte na vlákno v okně vlákna , čímž se nastaví jako aktuální. Aktuální vlákna mají žlutou šipku. Když změníte aktuální vlákno, jeho zásobník volání se zobrazí v okně zásobník volání .
Kontrola okna Paralelní zásobníky
v nabídce ladění přejděte na Windows a pak klikněte na paralelní zásobníky. Ujistěte se, že je v poli v levém horním rohu vybraná možnost vlákna .
Pomocí okna paralelní zásobníky můžete zobrazit více zásobníků volání současně v jednom zobrazení. Následující ilustrace znázorňuje okno paralelní zásobníky nad oknem zásobník volání .

Zásobník volání hlavního vlákna se zobrazí v jednom poli a zásobníky volání pro ostatní čtyři vlákna jsou seskupeny v jiném poli. Čtyři vlákna jsou seskupena dohromady, protože jejich rámce zásobníku sdílí stejné kontexty metod; To znamená, že jsou stejné metody:
A,BaC. Chcete-li zobrazit ID a názvy vláken, která sdílejí stejné pole, umístěte ukazatel myši na pole s hlavičkou (4 vlákna). Aktuální vlákno je zobrazeno tučně.
Žlutá šipka označuje aktivní rámec zásobníku aktuálního vlákna.
Kliknutím pravým tlačítkem myši v okně zásobník volání můžete nastavit, kolik podrobností se má zobrazit pro rámce zásobníku (názvy modulů, typy parametrů, názvy parametrů, hodnoty parametrů, čísla řádků a posun bajtů).
Modrý zvýraznění kolem pole indikuje, že aktuální vlákno je součástí tohoto pole. Aktuální vlákno je také vyznačeno tučným rámcem zásobníku v popisku. Pokud dvakrát kliknete na hlavní vlákno v okně vlákna, můžete si všimnout, že modrý zvýraznění v okně paralelní zásobníky se odpovídajícím způsobem přesune.

Pokračování v provádění až do druhé zarážky
Chcete-li pokračovat v provádění, dokud nebude dosaženo druhé zarážce, v nabídce ladění klikněte na tlačítko pokračovat. Následující ilustrace znázorňuje strom vláken ve druhé zarážce.

Na první zarážce byly čtyři vlákna z S. A až S. B až S. C. Tyto informace jsou stále viditelné v okně paralelní zásobníky , ale v těchto čtyřech vláknech probíhalo další postup. Jedna z nich pokračovala S. D a pak S.E. Asia Další pokračování na S. F, S. G a S.H. Dva ostatní pokračuje na S. I a S. J a z jednoho z nich se dostali na S. K a druhý neuživatelský externí kód.
Můžete umístit ukazatel myši na záhlaví pole, například 1 vlákno nebo 2 vlákna, a zobrazit tak ID vláken vláken. Můžete umístit ukazatel myši na snímky zásobníku a zobrazit ID vláken a další podrobnosti o snímku. Modrý zvýraznění indikuje aktuální vlákno a žlutá šipka označuje aktivní rámec zásobníku aktuálního vlákna.
Ikona tkanin – vlákna (proložené čáry) označují aktivní rámce zásobníku neaktuálních vláken. V okně Zásobník volání poklikejte na S.B a přepněte snímky. Okno Paralelní zásobníky označuje aktuální rámec zásobníku aktuálního vlákna pomocí zelené ikony se křivkou šipky.
V okně Vlákna přepněte mezi vlákny a sledujte, že je aktualizované zobrazení v okně Paralelní zásobníky.
Pomocí místní nabídky v okně Paralelní zásobníky můžete přepnout na jiné vlákno nebo jiný rámec jiného vlákna. Klikněte například pravým tlačítkem na S.J, přejděte na Přepnout na snímek a pak klikněte na příkaz.

Klikněte pravým tlačítkem na S.C a přejděte na Přepnout na rámec. Jeden z příkazů má značku zaškrtnutí, která označuje rámec zásobníku aktuálního vlákna. Můžete přepnout na tento rámec stejného vlákna (přesune se pouze zelená šipka) nebo můžete přepnout na druhé vlákno (modré zvýraznění se také přesune). Následující obrázek znázorňuje podnabídku.

Pokud je kontext metody přidružen pouze k jednomu snímku zásobníku, záhlaví pole zobrazí 1 vlákno a můžete na něj přepnout poklikáním. Pokud dvakrát kliknete na kontext metody, ke které je přidružený více než jeden snímek, automaticky se zobrazí nabídka. Při najetí myší na kontexty metody si všimněte černé trojúhelníku vpravo. Po kliknutí na trojúhelník se zobrazí také místní nabídka.
U velkých aplikací, které mají mnoho vláken, se můžete zaměřit pouze na podmnožinu vláken. Okno Paralelní zásobníky může zobrazit zásobníky volání pouze pro vlákna označená příznakem. Pokud chcete označit vlákna příznakem, použijte místní nabídku nebo první buňku vlákna.
Na panelu nástrojů klikněte na tlačítko Zobrazit jenom příznak vedle seznamu.

Nyní se v okně Paralelní zásobníky zobrazí pouze vlákno s příznakem.
Obnovení provádění až do třetí zarážky
Pokud chcete pokračovat v provádění, dokud se nedosáhne třetí zarážky, klikněte v nabídce Ladit na Pokračovat.
Pokud je více vláken ve stejné metodě, ale metoda nebyla na začátku zásobníku volání, metoda se zobrazí v různých polích. Příkladem aktuální zarážky je S.L, která obsahuje tři vlákna a zobrazuje se ve třech polích. Poklikejte na S.L.

Všimněte si, že V ostatních dvou polích je S.L tučným písmem, abyste viděli, kde se nachází. Pokud chcete zobrazit, které rámce volají do souboru S.L a které rámce volá, klikněte na panelu nástrojů na tlačítko Přepnout zobrazení metody. Následující obrázek znázorňuje zobrazení metody v okně Paralelní zásobníky.

Všimněte si, jak se diagram přetánul na vybranou metodu a umístěte ho do vlastního pole uprostřed zobrazení. Volaní a volající se zobrazují v horní a dolní části. Znovu klikněte na tlačítko Přepnout zobrazení metody, abyste tento režim opustili.
Místní nabídka okna Paralelní zásobníky obsahuje také následující další položky.
Šestnáctkové zobrazení přepíná čísla v popisech mezi desetinnou čárkou a šestnáctkovým číslem.
Symbol Nastavení příslušná dialogová okna.
Zobrazit vlákna ve zdroji přepíná zobrazení značek vláken ve zdrojovém kódu, který zobrazuje umístění vláken ve zdrojovém kódu.
Zobrazit externí kód zobrazí všechny snímky, i když nejsou v uživatelském kódu. Zkuste se podívat, jak se diagram rozbalí, aby vyhovoval dalším snímkům (což může být neaktivní, protože pro ně nemáte symboly).
V okně Paralelní zásobníky se ujistěte, že je na panelu nástrojů tlačítko Automaticky posouvat na aktuální rámec zásobníku.
Pokud máte velké diagramy a přistupte k další zarážce, můžete chtít, aby se zobrazení automaticky posunuly k aktivnímu snímku zásobníku aktuálního vlákna. To znamená vlákno, které se nejprve nachází na zarážce.
Než budete pokračovat, v okně Paralelní zásobníky se posuňte doleva a dolů.
Obnovení provádění až do čtvrté zarážky
Pokud chcete pokračovat v provádění, dokud se nedosáhne čtvrté zarážky, klikněte v nabídce Ladit na Pokračovat.
Všimněte si, jak se zobrazení automaticky posunula na místo. Přepínání vláken v okně Vlákna nebo přepínání snímků zásobníku v okně Zásobník volání a všimněte si, jak se zobrazení vždy automaticky posune na správný rámec. Vypněte možnost Automatické posouvání na aktuální rámec nástroje a podívejte se na rozdíl.
Zobrazení z ptačí oka také pomáhá s velkými diagramy v okně Paralelní zásobníky. Ve výchozím nastavení je zobrazení z ptačího oka povoleno. Můžete ho ale přepínat kliknutím na tlačítko mezi posuvníky v pravém dolním rohu okna, jak je znázorněno na následujícím obrázku.

V zobrazení z ptačí oka můžete obdélník přesunout, abyste diagram rychle posunuti.
Dalším způsobem, jak přesunout diagram v libovolném směru, je kliknout na prázdnou oblast diagramu a přetáhnout ho tam, kde ho chcete.
Pokud chcete diagram přiblížit nebo oddálit, stiskněte a podržte klávesu CTRL a pohybujte kolečkem myši. Případně můžete kliknout na tlačítko Lupa na panelu nástrojů a pak použít nástroj Lupa.
Zásobníky můžete zobrazit také ve směru shora dolů místo zdola nahoru, a to tak, že kliknete na nabídku Nástroje, kliknete na Možnosti a potom vyberete nebo vymažete možnost pod uzlem Ladění.
Než budete pokračovat, klikněte v nabídce Ladit na Zastavit ladění, aby se provádění zastavilo.
Použití okna Paralelní úlohy a zobrazení úloh v okně Paralelní zásobníky
Než budete pokračovat, doporučujeme, abyste dokončili předchozí postupy.
Restartování aplikace, dokud se nedosáhne první zarážky
V nabídce Ladit klikněte na Spustit ladění a počkejte na přístup k první zarážce.
V nabídce Ladit přejděte na Windows a pak klikněte na Vlákna. Ukotvení okna Vlákna v dolní části Visual Studio.
V nabídce Ladit přejděte na Windows klikněte na Call Stack (Zásobník volání). Ukotvení okna Zásobník volání v dolní části Visual Studio.
Poklikejte na vlákno v okně Vlákna, aby bylo aktuální. Aktuální vlákna mají žlutou šipku. Když změníte aktuální vlákno, ostatní okna se aktualizují. Dále se budeme zabývat úkoly.
V nabídce Ladit přejděte na Windows a pak klikněte na Úlohy. Následující obrázek znázorňuje okno Úlohy.

Pro každou spuštěnou úlohu si můžete přečíst jeho ID vrácené vlastností se stejným názvem, ID a název vlákna, které ho spouští, jeho umístění (najetím myší na zobrazíte popis, který obsahuje celý zásobník volání). Také ve sloupci Úloha vidíte metodu , která byla předána do úlohy. Jinými slovy, výchozí bod.
Můžete seřadit libovolný sloupec. Všimněte si piktogramu řazení, který označuje sloupec řazení a směr. Sloupce můžete také změnit jejich přetažením doleva nebo doprava.
Žlutá šipka označuje aktuální úkol. Úkoly můžete přepínat dvojím kliknutím na úlohu nebo pomocí místní nabídky. Když přepnete úlohy, podkladové vlákno se změní na aktuální a ostatní okna se aktualizují.
Když ručně přepnete z jednoho úkolu na jiný, žlutá šipka se přesune, ale bílá šipka stále zobrazuje úlohu, která způsobila přerušení ladicího programu.
Obnovení provádění až do druhé zarážky
Pokud chcete pokračovat v provádění, dokud se nedosáhne druhé zarážky, klikněte v nabídce Ladit na Pokračovat.
Dříve sloupec Stav ukázal všechny úkoly jako Aktivní, ale teď jsou dva z těchto úkolů Zablokované. Úlohy je možné blokovat z mnoha různých důvodů. Ve sloupci Stav najeďte myší na čekající úlohu a zjistěte, proč je blokovaná. Například na následujícím obrázku úkol 3 čeká na úkol 4.

Úkol 4 zase čeká na monitorování vlastněné vláknem přiřazeným k úkolu 2. (Klikněte pravým tlačítkem na řádek záhlaví a zvolte Sloupce. > Přiřazení vlákna k zobrazení hodnoty přiřazení vlákna pro úkol 2).

Úlohu můžete označit příznakem kliknutím na příznak v prvním sloupci okna Úlohy.
Označení můžete použít ke sledování úkolů mezi různými zarážkami ve stejné ladicí relaci nebo k filtrování úkolů, jejichž zásobníky volání se zobrazují v okně Paralelní zásobníky.
Když jste dříve použili okno Paralelní zásobníky, prohlíželi jste vlákna aplikace. Znovu si prohlédněte okno Paralelní zásobníky, ale tentokrát si prohlédněte úlohy aplikace. Proveďte to tak, že v poli vlevo nahoře vyberete Úlohy. Následující obrázek znázorňuje zobrazení Úlohy.

Vlákna, která aktuálně neschová úlohy, se v zobrazení Úloh v okně Paralelní zásobníky nezobrazí. V případě vláken, která spouštějí úkoly, jsou také z horního a dolního zásobníku filtrovány některé rámce zásobníku, které nejsou relevantní pro úlohy.
Znovu zobrazte okno úlohy . Kliknutím pravým tlačítkem myši na záhlaví kteréhokoli sloupce zobrazíte místní nabídku pro daný sloupec.
Pomocí místní nabídky můžete přidat nebo odebrat sloupce. Například není vybrán sloupec AppDomain. Proto se v seznamu nezobrazí. Klikněte na Nadřazená položka. Nadřazený sloupec se zobrazí bez hodnot pro žádnou ze čtyř úkolů.
Chcete-li pokračovat v provádění až po třetí zarážku
Chcete-li pokračovat v provádění, dokud nebude dosaženo třetí zarážce, v nabídce ladění klikněte na tlačítko pokračovat.
Nová úloha, úloha 5, je teď spuštěná a úloha 4 teď čeká. Můžete se podívat, proč na čekající úkol ve stavovém okně najede myší. V nadřazeném sloupci si všimněte, že úloha 4 představuje nadřazený úkol 5.
Chcete-li lépe vizualizovat vztah nadřazený-podřízený, klikněte pravým tlačítkem myši na řádek záhlaví sloupce a poté klikněte na položku nadřazené podřízené zobrazení. Měl by se zobrazit následující obrázek.

Všimněte si, že úloha 4 a úloha 5 jsou spuštěny ve stejném vlákně (zobrazení sloupce přiřazení vlákna , je-li skrytý). Tyto informace nejsou zobrazeny v okně vlákna . Podívejte se, že se jedná o další výhody okna úlohy . Potvrďte to tak, že zobrazíte okno paralelní zásobníky . Ujistěte se, že prohlížíte úkoly. Vyhledejte úkoly 4 a 5 dvojitým kliknutím na ně v okně úlohy . V takovém případě se modrý zvýraznění v okně paralelní zásobníky aktualizuje. Můžete také vyhledat úlohy 4 a 5 kontrolou tlačítek v okně paralelní zásobníky .

V okně paralelní zásobníky klikněte pravým tlačítkem na S. P a pak klikněte na Přejít ke vláknu. Okno se přepne do zobrazení vlákna a odpovídající rámec je v zobrazení. Ve stejném vlákně vidíte obě úlohy.

Toto je další výhodou zobrazení úlohy v okně paralelní zásobníky ve srovnání s oknem vláken .
Pokračování v provádění až do čtvrté zarážky
Chcete-li pokračovat v provádění, dokud nebude dosaženo třetí zarážce, v nabídce ladění klikněte na tlačítko pokračovat. Kliknutím na záhlaví sloupce ID seřadíte podle ID. Měl by se zobrazit následující obrázek.

Protože byl úkol 5 dokončen, již není zobrazen. Pokud se nejedná o případ na vašem počítači a zablokování se nezobrazuje, proveďte krok jednou stisknutím klávesy F11.
Úloha 3 a úloha 4 nyní čekají na sebe a jsou blokovány. K dispozici jsou také 5 nových úloh, které jsou podřízenými položkami úlohy 2 a jsou nyní naplánovány. Naplánované úlohy jsou úlohy, které byly spuštěny v kódu, ale ještě nebyly spuštěny. Proto jsou sloupce přiřazení umístění a vlákna prázdné.
Znovu zobrazte okno paralelní zásobníky . Záhlaví každého pole má popisek, který zobrazuje ID a názvy vláken. Přepněte do zobrazení úlohy v okně paralelní zásobníky . Když najedete myší na záhlaví, zobrazí se ID a název úlohy a stav úlohy, jak je znázorněno na následujícím obrázku.

Úkoly můžete seskupit podle sloupce. V okně úlohy klikněte pravým tlačítkem myši na záhlaví sloupce stav a pak klikněte na možnost Seskupit podle stav. Následující ilustrace znázorňuje okno úlohy seskupené podle stavu.

Můžete také seskupit podle libovolného jiného sloupce. Seskupením úloh se můžete soustředit na podmnožinu úkolů. Každá sbalitelná skupina má počet položek, které jsou seskupeny dohromady.
Poslední funkcí okna úlohy k prohlédnutí je místní nabídka, která se zobrazí po kliknutí pravým tlačítkem myši na úlohu.
Místní nabídka zobrazuje různé příkazy v závislosti na stavu úlohy. Příkazy mohou zahrnovat kopírování, Výběr všech, hexadecimálního zobrazení, Přepnutí na úlohu, zablokování přiřazeného vlákna, zablokování všech vláken, ale toto a příznak.
Můžete zablokovat základní vlákno úlohy nebo úlohy nebo můžete zablokovat všechna vlákna s výjimkou přiřazeného. Zmrazené vlákno je v okně úlohy reprezentováno, protože je v okně vlákna pomocí modré ikony pauzy .
Souhrn
Tento názorný postup ukázal okna paralelních úkolů a paralelních zásobníků pro ladicí program. Používejte tato okna na skutečných projektech, které používají vícevláknový kód. Můžete kontrolovat paralelní kód napsaný v jazyce C++, C# nebo Visual Basic.