.NET Tools

JetBrains Rider에서 Tasks(작업) 뷰를 사용하는 방법

Read this post in other languages:

Task Parallel Library(TPL)는 프레임워크가 멀티스레드 병렬 코드를 작성하고 실행할 수 있도록 하므로 모든 .NET 애플리케이션에 필수적입니다. 또한, 리소스를 최대한 활용하려는 개발자라면 System.ThreadingSystem.Threading.Tasks에 있는 타입을 활용하여 맞춤형 코드를 작성하고 싶을 것입니다. 이를 위해서는 잠금, 교착 상태, await 및 스케줄링과 같이 빠르고 확장 가능한 솔루션을 작성할 때 필요한 동시성 및 스레드의 기초를 이해해야 합니다. 이러한 개념에 대한 이해를 확장하려면 혼란스럽게 실행되는 작업을 시각화하고 이해하는 데 도움을 주는 탁월한 도구가 필요합니다.

좋은 소식이 있습니다! 최근 Tasks(작업) 뷰의 첫 번째 신규 버전이 출시되었습니다. Tasks 뷰는 현재 애플리케이션 프로세스 내 작업을 이해할 수 있도록 도와주는 강력한 도구입니다.

이 글에서는 새로운 도구 창을 살펴보고, 필수 UI 요소를 논의한 다음, 일반적인 시나리오를 몇 가지 설명합니다. 다 읽고 난 후 여러분은 자신의 코드 베이스를 살펴보며 최적화 가능성을 찾을 수 있게 될 것입니다.

.NET 애플리케이션에서 작업 실행

.NET에서 Tasks(작업)는 동시성 및 멀티스레딩과 같은 개념에 대한 추상화를 제공합니다. 목적은 CPU 코어나 스레드에 대한 걱정을 줄이고 스케줄링이나 작업 동시 실행과 같은 더 상위의 개념에 집중하도록 하는 것입니다. 이는 개발자들이 시스템 리소스를 효율적으로 활용하면서 명령형 코드를 작성하는 데 집중하도록 도와주기 때문에 보통은 매우 유용합니다.

그러나 그 유용성에도 불구하고 완벽한 추상화란 없으며 특정 시점에는 여러 작업을 동시에 실행했을 때의 위험을 관리해야 합니다. 여기에는 교착 상태, 경합 조건 및 부하를 야기하는 비효율적인 스케줄링 등이 포함됩니다. 추상화를 사용하는 것과 별도로 그 작동 방식 및 원리도 이해하면 좋습니다.

일반적으로 사용자는 작업의 스케줄링을 처리하지만 작업이 언제 어떻게 끝날지는 알 수 없습니다. 실행은 .NET 런타임이 담당합니다. 문제가 발생할 징후를 보이는 특정 코드 패턴을 식별할 수도 있지만, 가장 좋은 방법은 런타임에 작업의 문제를 진단하는 것입니다. 그러면 코드 베이스에서 일반적으로 발견될 수 있는 몇몇 시나리오를 살펴보고 Tasks 뷰로 애플리케이션을 이해하는 방법을 알아보겠습니다.

공통 작업

작업을 다룰 때 가장 가능성이 높은 시나리오는 asyncawait 키워드가 필요한 작업을 반환하는 API를 사용하는 것입니다. 이러한 비동기 API는 ASP.NET Core, MAUI 및 Entity Framework Core에 있습니다. 여러 오픈 소스 프로젝트도 async를 우선하는 API로 전환하여 개발자의 요구 사항에 대응하고 있습니다.

간단한 예시를 살펴보겠습니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<span class="keyword">await</span> <span class="method-name">BasicWork</span>();
<span class="keyword">async</span> <span class="class-name">Task</span> <span class="method-name">BasicWork</span>()
{
<span class="keyword">await</span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Delay</span>(<span class="struct-name">TimeSpan</span><span class="operator">.</span><span class="method-name static-symbol">FromSeconds</span>(<span class="number">1</span>));
<span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"? Hello Tasks View!"</span>);
}
<span class="keyword">await</span> <span class="method-name">BasicWork</span>(); <span class="keyword">async</span> <span class="class-name">Task</span> <span class="method-name">BasicWork</span>() { <span class="keyword">await</span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Delay</span>(<span class="struct-name">TimeSpan</span><span class="operator">.</span><span class="method-name static-symbol">FromSeconds</span>(<span class="number">1</span>)); <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"? Hello Tasks View!"</span>); }
<span class="keyword">await</span> <span class="method-name">BasicWork</span>();

<span class="keyword">async</span> <span class="class-name">Task</span> <span class="method-name">BasicWork</span>()
{
    <span class="keyword">await</span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Delay</span>(<span class="struct-name">TimeSpan</span><span class="operator">.</span><span class="method-name static-symbol">FromSeconds</span>(<span class="number">1</span>));
    <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"? Hello Tasks View!"</span>);
}
클립보드에 복사

이 예시에는 두 개의 작업이 있습니다. JetBrains의 프로그램에서 가져온 메인 작업과 BasicWork 메서드가 있습니다. 이를 새로운 Tasks(작업) 뷰를 사용하여 확인할 수 있습니다. 세션을 디버그하는 동안 Task 탭을 눌러 다음의 테이블을 표시합니다.

JetBrains Rider의 Table 모드 Tasks 뷰

또한, 우측 상단에서 Graph(그래프) 뷰를 선택하여 전환할 수 있습니다.

JetBrains Rider의 Graph 모드 Tasks 뷰

Graph 뷰에서 스택 항목에 마우스 커서를 올려 놓으면 행의 정보를 확인할 수 있습니다.

Graph 뷰에서 행 표시하기

Tasks로 작업할 때 작업의 상태는 다음 다섯 가지 중 하나입니다.

  • Active(활성화): 현재 실행 중인 상태입니다.
  • Scheduled(예약됨): 작업이 생성되었으나 아직 실행되지 않은 상태입니다.
  • Awaiting(대기 중): 작업이 대기 중인 상태이나 다른 작업을 기다리고 있는 중일 수 있습니다.
  • Blocked(차단됨): 작업이 스택의 상단에 있으며 이 작업을 실행하는 스레드가 차단된 상태입니다(휴면, 잠금 대기 등). 스택상에서 더 높이 위치한 다른 작업이 있을 수도 있습니다.
  • Deadlocked(교착): 작업이 하나의 리소스를 두고 경합하고 있으며, 중대한 문제가 있는 상태입니다.

가장 일반적인 시나리오를 살펴보았으니 이제 상위 작업을 살펴보겠습니다.

상위 작업

상위 작업을 생성하면 논리적으로 작업을 그룹화할 수 있습니다. 작업 내에 작업을 생성할 때는 TaskCreationOptions.AttachedToParent를 사용해서 새로운 작업을 이를 포함하는 작업에 연결할 수 있습니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<span class="keyword">await</span> <span class="method-name">ParentedTasks</span>();
<span class="class-name">Task</span> <span class="method-name">ParentedTasks</span>()
{
<span class="comment">// Parent task</span>
<span class="keyword">var</span> <span class="local-name">parentTask</span> <span class="operator">=</span> <span class="class-name">Task</span><span class="operator">.</span><span class="property-name static-symbol">Factory</span><span class="operator">.</span><span class="method-name">StartNew</span>(() <span class="operator">=></span>
{
<span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="static-symbol method-name">WriteLine</span>(<span class="string">"Parent task started."</span>);
<span class="comment">// Child task</span>
<span class="keyword">var</span> <span class="local-name">task</span> <span class="operator">=</span> <span class="class-name">Task</span><span class="operator">.</span><span class="property-name static-symbol">Factory</span><span class="operator">.</span><span class="method-name">StartNew</span>(() <span class="operator">=></span>
{
<span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Child task started."</span>);
<span class="class-name">Task</span><span class="operator">.</span><span class="static-symbol method-name">Delay</span>(<span class="number">2000</span>)<span class="operator">.</span><span class="method-name">Wait</span>(); <span class="comment">// Simulating some work</span>
<span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Child task completed."</span>);
}, <span class="enum-name">TaskCreationOptions</span><span class="operator">.</span><span class="enum-member-name">AttachedToParent</span>);
<span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Parent task doing some work."</span>);
}, <span class="enum-name">TaskCreationOptions</span><span class="operator">.</span><span class="enum-member-name">AttachedToParent</span>);
<span class="comment">// Wait for parent task to complete, which includes the children</span>
<span class="local-name">parentTask</span><span class="operator">.</span><span class="method-name">Wait</span>();
<span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Parent task completed."</span>);
<span class="keyword-control">return</span> <span class="class-name">Task</span><span class="operator">.</span><span class="property-name static-symbol">CompletedTask</span>;
}
<span class="keyword">await</span> <span class="method-name">ParentedTasks</span>(); <span class="class-name">Task</span> <span class="method-name">ParentedTasks</span>() { <span class="comment">// Parent task</span> <span class="keyword">var</span> <span class="local-name">parentTask</span> <span class="operator">=</span> <span class="class-name">Task</span><span class="operator">.</span><span class="property-name static-symbol">Factory</span><span class="operator">.</span><span class="method-name">StartNew</span>(() <span class="operator">=></span> { <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="static-symbol method-name">WriteLine</span>(<span class="string">"Parent task started."</span>); <span class="comment">// Child task</span> <span class="keyword">var</span> <span class="local-name">task</span> <span class="operator">=</span> <span class="class-name">Task</span><span class="operator">.</span><span class="property-name static-symbol">Factory</span><span class="operator">.</span><span class="method-name">StartNew</span>(() <span class="operator">=></span> { <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Child task started."</span>); <span class="class-name">Task</span><span class="operator">.</span><span class="static-symbol method-name">Delay</span>(<span class="number">2000</span>)<span class="operator">.</span><span class="method-name">Wait</span>(); <span class="comment">// Simulating some work</span> <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Child task completed."</span>); }, <span class="enum-name">TaskCreationOptions</span><span class="operator">.</span><span class="enum-member-name">AttachedToParent</span>); <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Parent task doing some work."</span>); }, <span class="enum-name">TaskCreationOptions</span><span class="operator">.</span><span class="enum-member-name">AttachedToParent</span>); <span class="comment">// Wait for parent task to complete, which includes the children</span> <span class="local-name">parentTask</span><span class="operator">.</span><span class="method-name">Wait</span>(); <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Parent task completed."</span>); <span class="keyword-control">return</span> <span class="class-name">Task</span><span class="operator">.</span><span class="property-name static-symbol">CompletedTask</span>; }
<span class="keyword">await</span> <span class="method-name">ParentedTasks</span>();

<span class="class-name">Task</span> <span class="method-name">ParentedTasks</span>()
{
    <span class="comment">// Parent task</span>
    <span class="keyword">var</span> <span class="local-name">parentTask</span> <span class="operator">=</span> <span class="class-name">Task</span><span class="operator">.</span><span class="property-name static-symbol">Factory</span><span class="operator">.</span><span class="method-name">StartNew</span>(() <span class="operator">=></span>
    {
        <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="static-symbol method-name">WriteLine</span>(<span class="string">"Parent task started."</span>);

        <span class="comment">// Child task</span>
        <span class="keyword">var</span> <span class="local-name">task</span> <span class="operator">=</span> <span class="class-name">Task</span><span class="operator">.</span><span class="property-name static-symbol">Factory</span><span class="operator">.</span><span class="method-name">StartNew</span>(() <span class="operator">=></span>
        {
            <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Child task started."</span>);
            <span class="class-name">Task</span><span class="operator">.</span><span class="static-symbol method-name">Delay</span>(<span class="number">2000</span>)<span class="operator">.</span><span class="method-name">Wait</span>(); <span class="comment">// Simulating some work</span>
            <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Child task completed."</span>);
        }, <span class="enum-name">TaskCreationOptions</span><span class="operator">.</span><span class="enum-member-name">AttachedToParent</span>);

        <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Parent task doing some work."</span>);
    }, <span class="enum-name">TaskCreationOptions</span><span class="operator">.</span><span class="enum-member-name">AttachedToParent</span>);

    <span class="comment">// Wait for parent task to complete, which includes the children</span>
    <span class="local-name">parentTask</span><span class="operator">.</span><span class="method-name">Wait</span>();

    <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Parent task completed."</span>);
    <span class="keyword-control">return</span> <span class="class-name">Task</span><span class="operator">.</span><span class="property-name static-symbol">CompletedTask</span>;
}
클립보드에 복사

코드를 실행한 다음 Task(작업) 뷰를 보면 하위 작업이 상위에 성공적으로 추가된 것을 확인할 수 있습니다.

다른 작업의 상위가 된 작업 표시

작업이 새로 생성될 때마다 .NET 런타임이 정수 Id를 할당한다는 점에 유의하세요. 이러한 식별자로 현재 프로세스 내에 있는 작업을 추적할 수 있습니다.

이번에는 Graph(그래프) 뷰에 두 개의 비동기 논리 스택이 표시되어 있습니다. 이 스택은 Wait 및 반환하는 Task.CompletedTask와 같은 메서드를 사용하는 ParentedTasks 코드의 결과로 발생합니다.

두 개의 비동기 논리 스택 표시

좋습니다. 작업이 서로 관련이 있거나 별도의 논리 스택을 생성했는지 파악하면 잠재적인 경합 조건이 생성되었는지 판단할 수 있습니다.

이제 Tasks 뷰로 작업이 어떻게 예약되었는지 확인하는 방법을 알아보겠습니다.

작업 스케줄링

언제든지 작업을 await 상태로 설정하면 작업이 추후에 실행되도록 예약됩니다. 이는 작업이 예약된 후 즉시 또는 다른 예약 작업이 실행된 후에 일어납니다. 몇몇 작업을 예약하고 해당 작업이 완료될 때까지 기다리는 예시를 살펴보겠습니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<span class="keyword">await</span> <span class="method-name">ScheduledWork</span>();
<span class="keyword">async</span> <span class="class-name">Task</span> <span class="method-name">ScheduledWork</span>()
{
<span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="static-symbol method-name">Write</span>(<span class="string">"Let's work..."</span>);
<span class="keyword">var</span> <span class="local-name">tasks</span> <span class="operator">=</span> <span class="class-name static-symbol">Enumerable</span>
<span class="operator">.</span><span class="static-symbol method-name">Range</span>(<span class="number">1</span>, <span class="number">10</span>)
<span class="operator">.</span><span class="extension-method-name">Select</span>((<span class="parameter-name">i</span>) <span class="operator">=></span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Run</span>(() <span class="operator">=></span> <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">Write</span>(<span class="parameter-name">i</span>)));
<span class="keyword">await</span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">WhenAll</span>(<span class="local-name">tasks</span>);
}
<span class="keyword">await</span> <span class="method-name">ScheduledWork</span>(); <span class="keyword">async</span> <span class="class-name">Task</span> <span class="method-name">ScheduledWork</span>() { <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="static-symbol method-name">Write</span>(<span class="string">"Let's work..."</span>); <span class="keyword">var</span> <span class="local-name">tasks</span> <span class="operator">=</span> <span class="class-name static-symbol">Enumerable</span> <span class="operator">.</span><span class="static-symbol method-name">Range</span>(<span class="number">1</span>, <span class="number">10</span>) <span class="operator">.</span><span class="extension-method-name">Select</span>((<span class="parameter-name">i</span>) <span class="operator">=></span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Run</span>(() <span class="operator">=></span> <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">Write</span>(<span class="parameter-name">i</span>))); <span class="keyword">await</span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">WhenAll</span>(<span class="local-name">tasks</span>); }
<span class="keyword">await</span> <span class="method-name">ScheduledWork</span>();
<span class="keyword">async</span> <span class="class-name">Task</span> <span class="method-name">ScheduledWork</span>()
{
    <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="static-symbol method-name">Write</span>(<span class="string">"Let's work..."</span>);
    <span class="keyword">var</span> <span class="local-name">tasks</span> <span class="operator">=</span> <span class="class-name static-symbol">Enumerable</span>
        <span class="operator">.</span><span class="static-symbol method-name">Range</span>(<span class="number">1</span>, <span class="number">10</span>)
        <span class="operator">.</span><span class="extension-method-name">Select</span>((<span class="parameter-name">i</span>) <span class="operator">=></span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Run</span>(() <span class="operator">=></span> <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">Write</span>(<span class="parameter-name">i</span>)));

    <span class="keyword">await</span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">WhenAll</span>(<span class="local-name">tasks</span>);
}
클립보드에 복사

Task.WhenAll은 제공된 모든 작업을 실행하려고 시도하며, 이 모든 작업을 추후에 실행하도록 예약합니다. 이는 Tasks(작업) 뷰에서 확인할 수 있습니다.

생성된 10개의 작업 중 예약된 작업

추가적으로 Task.WhenAll을 사용하면 이러한 연산이 실행되는 구조인 비동기 논리 스택이 생성됩니다.

두 개의 비동기 논리 스택. 이 중 하나는 10개의 값을 표시

디버그 세션 중 코드를 단계별로 실행하면 작업이 완료되면서 작업 목록이 줄어드는 것을 볼 수 있습니다. 또한, 여러 작업이 동시에 실행되는 것도 확인할 수 있습니다.

작업이 줄어드는 것을 보여주는 Tasks 뷰

작업이 완료되는 것은 만족스럽지만 너무 오래 지속된다면 좋지 않습니다. 그러면 작업을 다룰 때 가장 무서운 시나리오인 교착 상태로 넘어가겠습니다.

교착 상태

교착 상태를 일으키는 가장 일반적인 이유는 잠금 메커니즘으로 보호되는 공유 리소스를 두고 경합이 발생하기 때문입니다. 공유 리소스를 처리할 때 잠금은 필수적이지만 앱을 손상시키는 문제를 일으킬 수 있습니다.

스릴 있는 삶도 재미는 있으니 교착 상태를 한 번 일으켜 보겠습니다. 더 중요한 목적은 Tasks(작업) 뷰로 교착 상태를 어떻게 식별하는지 알아보는 것입니다. 같은 변수를 잠그려고 시도하는 두 개의 작업을 예약하겠습니다.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<span class="keyword">await</span> <span class="method-name">Deadlock</span>();
<span class="comment">// This method will cause a deadlock</span>
<span class="comment">// proceed with caution, oOOoOOoOo! ?</span>
<span class="keyword">async</span> <span class="class-name">Task</span> <span class="method-name">Deadlock</span>()
{
<span class="keyword">object</span> <span class="local-name">one</span> <span class="operator">=</span> <span class="keyword">new</span>();
<span class="keyword">object</span> <span class="local-name">two</span> <span class="operator">=</span> <span class="keyword">new</span>();
<span class="keyword">var</span> <span class="local-name">timer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="namespace-name">System</span><span class="operator">.</span><span class="namespace-name">Timers</span><span class="operator">.</span><span class="class-name">Timer</span>(
<span class="struct-name">TimeSpan</span><span class="operator">.</span><span class="method-name static-symbol">FromSeconds</span>(<span class="number">2</span>)
) { <span class="property-name">Enabled</span> <span class="operator">=</span> <span class="keyword">true</span>, <span class="property-name">AutoReset</span> <span class="operator">=</span> <span class="keyword">false</span> };
<span class="local-name">timer</span><span class="operator">.</span><span class="event-name">Elapsed</span> <span class="operator">+=</span> (<span class="keyword">_</span>, <span class="keyword">_</span>) <span class="operator">=></span>
{
<span class="comment">// only see this if we're deadlocked</span>
<span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"?Deadlock"</span>);
};
<span class="keyword">await</span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">WhenAll</span>(<span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Run</span>(() <span class="operator">=></span>
{
<span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="static-symbol method-name">WriteLine</span>(<span class="string">"Getting lock for one."</span>);
<span class="keyword">lock</span> (<span class="local-name">one</span>)
{
<span class="class-name">Thread</span><span class="operator">.</span><span class="static-symbol method-name">Sleep</span>(<span class="number">1000</span>);
<span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="static-symbol method-name">WriteLine</span>(<span class="string">"Getting lock two in first task."</span>);
<span class="keyword">lock</span> (<span class="local-name">two</span>)
{
}
}
}), <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Run</span>(() <span class="operator">=></span>
{
<span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Getting lock two in second task."</span>);
<span class="keyword">lock</span> (<span class="local-name">two</span>)
{
<span class="class-name">Thread</span><span class="operator">.</span><span class="method-name static-symbol">Sleep</span>(<span class="number">1000</span>);
<span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Getting lock one in second task."</span>);
<span class="keyword">lock</span> (<span class="local-name">one</span>)
{
}
}
}));
}
<span class="keyword">await</span> <span class="method-name">Deadlock</span>(); <span class="comment">// This method will cause a deadlock</span> <span class="comment">// proceed with caution, oOOoOOoOo! ?</span> <span class="keyword">async</span> <span class="class-name">Task</span> <span class="method-name">Deadlock</span>() { <span class="keyword">object</span> <span class="local-name">one</span> <span class="operator">=</span> <span class="keyword">new</span>(); <span class="keyword">object</span> <span class="local-name">two</span> <span class="operator">=</span> <span class="keyword">new</span>(); <span class="keyword">var</span> <span class="local-name">timer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="namespace-name">System</span><span class="operator">.</span><span class="namespace-name">Timers</span><span class="operator">.</span><span class="class-name">Timer</span>( <span class="struct-name">TimeSpan</span><span class="operator">.</span><span class="method-name static-symbol">FromSeconds</span>(<span class="number">2</span>) ) { <span class="property-name">Enabled</span> <span class="operator">=</span> <span class="keyword">true</span>, <span class="property-name">AutoReset</span> <span class="operator">=</span> <span class="keyword">false</span> }; <span class="local-name">timer</span><span class="operator">.</span><span class="event-name">Elapsed</span> <span class="operator">+=</span> (<span class="keyword">_</span>, <span class="keyword">_</span>) <span class="operator">=></span> { <span class="comment">// only see this if we're deadlocked</span> <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"?Deadlock"</span>); }; <span class="keyword">await</span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">WhenAll</span>(<span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Run</span>(() <span class="operator">=></span> { <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="static-symbol method-name">WriteLine</span>(<span class="string">"Getting lock for one."</span>); <span class="keyword">lock</span> (<span class="local-name">one</span>) { <span class="class-name">Thread</span><span class="operator">.</span><span class="static-symbol method-name">Sleep</span>(<span class="number">1000</span>); <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="static-symbol method-name">WriteLine</span>(<span class="string">"Getting lock two in first task."</span>); <span class="keyword">lock</span> (<span class="local-name">two</span>) { } } }), <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Run</span>(() <span class="operator">=></span> { <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Getting lock two in second task."</span>); <span class="keyword">lock</span> (<span class="local-name">two</span>) { <span class="class-name">Thread</span><span class="operator">.</span><span class="method-name static-symbol">Sleep</span>(<span class="number">1000</span>); <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Getting lock one in second task."</span>); <span class="keyword">lock</span> (<span class="local-name">one</span>) { } } })); }
<span class="keyword">await</span> <span class="method-name">Deadlock</span>();

<span class="comment">// This method will cause a deadlock</span>
<span class="comment">// proceed with caution, oOOoOOoOo! ?</span>
<span class="keyword">async</span> <span class="class-name">Task</span> <span class="method-name">Deadlock</span>()
{
    <span class="keyword">object</span> <span class="local-name">one</span> <span class="operator">=</span> <span class="keyword">new</span>();
    <span class="keyword">object</span> <span class="local-name">two</span> <span class="operator">=</span> <span class="keyword">new</span>();

    <span class="keyword">var</span> <span class="local-name">timer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="namespace-name">System</span><span class="operator">.</span><span class="namespace-name">Timers</span><span class="operator">.</span><span class="class-name">Timer</span>(
        <span class="struct-name">TimeSpan</span><span class="operator">.</span><span class="method-name static-symbol">FromSeconds</span>(<span class="number">2</span>)
    ) { <span class="property-name">Enabled</span> <span class="operator">=</span> <span class="keyword">true</span>, <span class="property-name">AutoReset</span> <span class="operator">=</span> <span class="keyword">false</span> };

    <span class="local-name">timer</span><span class="operator">.</span><span class="event-name">Elapsed</span> <span class="operator">+=</span> (<span class="keyword">_</span>, <span class="keyword">_</span>) <span class="operator">=></span>
    {
        <span class="comment">// only see this if we're deadlocked</span>
        <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"?Deadlock"</span>);
    };

    <span class="keyword">await</span> <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">WhenAll</span>(<span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Run</span>(() <span class="operator">=></span>
    {
        <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="static-symbol method-name">WriteLine</span>(<span class="string">"Getting lock for one."</span>);
        <span class="keyword">lock</span> (<span class="local-name">one</span>)
        {
            <span class="class-name">Thread</span><span class="operator">.</span><span class="static-symbol method-name">Sleep</span>(<span class="number">1000</span>);
            <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="static-symbol method-name">WriteLine</span>(<span class="string">"Getting lock two in first task."</span>);
            <span class="keyword">lock</span> (<span class="local-name">two</span>)
            {
            }
        }
    }), <span class="class-name">Task</span><span class="operator">.</span><span class="method-name static-symbol">Run</span>(() <span class="operator">=></span>
    {
        <span class="static-symbol class-name">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Getting lock two in second task."</span>);
        <span class="keyword">lock</span> (<span class="local-name">two</span>)
        {
            <span class="class-name">Thread</span><span class="operator">.</span><span class="method-name static-symbol">Sleep</span>(<span class="number">1000</span>);
            <span class="class-name static-symbol">Console</span><span class="operator">.</span><span class="method-name static-symbol">WriteLine</span>(<span class="string">"Getting lock one in second task."</span>);
            <span class="keyword">lock</span> (<span class="local-name">one</span>)
            {
            }
        }
    }));
}
클립보드에 복사

코드를 실행하면 앱이 종료되지 않는다는 것을 확인할 수 있습니다. Run(실행) 툴바에서 일시 중지 버튼을 눌러 애플리케이션을 일시 중지하세요. 이런, 교착 상태네요! 충격적이에요! (물론 그렇게 놀라진 않았습니다)

Tasks Table 뷰에 표시된 한 쌍의 교착 상태에 빠진 작업

두 개의 작업이 서로 경쟁하고 있고 어떻게 이 상황이 발생했는지 보여주는 Graph(그래프) 뷰는 더 흥미롭습니다.

Tasks의 Graph 뷰에 표시된 한 쌍의 교착 상태에 빠진 작업

교착 상태에 빠진 논리 스택을 두 번 클릭하면 교착 상태가 있는 위치로 이동합니다.

JetBrains Rider에서 교착 상태를 일으키는 코드를 표시

편리한 탐색 기능으로 교착 상태를 손쉽게 찾고 해결할 수 있습니다.

결론

작업 뷰는 현재 JetBrains Rider 2024.2 EAP에서 사용할 수 있습니다. 이 도구의 미래를 만들 수 있도록 피드백을 공유해 주세요. JetBrains는 .NET 개발에서 작업을 처리하기가 상당히 어려울 수 있다는 사실을 잘 알고 있습니다. 이 추가 도구가 어려움을 극복하는 데 도움이 되길 바랍니다. 직접 사용해 보고 기존의 코드를 최적화하거나 코드 베이스에 있던 오래된 문제를 찾는 데 도움이 되는지 확인해 보세요.

읽어주셔서 감사드리며 여러분의 의견과 댓글을 기대하겠습니다.

이미지 출처: Eden Constantino

게시물 원문 작성자

image description

Discover more