Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

들어가며

참고

Thread & Task

coroutine

https://docs.unity3d.com/Manual/Coroutines.html

StartCoroutine https://github.com/Unity-Technologies/UnityCsReference/blob/e740821767d2290238ea7954457333f06e952bad/Runtime/Export/Scripting/MonoBehaviour.bindings.cs#L81

Task.Factory.StartNew(action)
	Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);

Task.Run(action)
	Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);


TaskCreationOptions.None            TaskScheduler.Current
TaskCreationOptions.DenyChildAttach TaskScheduler.Default

Task

TaskScheduler

SynchronizationContext

The idea behind ISynchronizeInvoke is that a “source” thread can queue a delegate to a “target” thread, optionally waiting for that delegate to complete. ASP.NET asynchronous pages aren’t associated with a single thread. Instead of queuing work to the original thread, asynchronous pages only need to maintain a count of outstanding operations to determine when the page request can be completed. After much thought and careful design, ISynchronizeInvoke was replaced by SynchronizationContext.

provides a way to queue a unit of work to a context. - SynchronizationContext does not include a mechanism to determine if synchronization is necessary, because this isn’t always possible. every thread has a “current” context. it keeps a count of outstanding asynchronous operations

ConfigureAwait provides a means to avoid the default SynchronizationContext capturing behavior; passing false for the flowContext parameter prevents the SynchronizationContext from being used to resume execution after the await. There’s also an extension method on SynchronizationContext instances called SwitchTo; this allows any async method to change to a different SynchronizationContext by invoking SwitchTo and awaiting the result.

ConfigureAwait

async stream

TAP

Task-based Asynchronous Pattern

Target Versionlanguage versionUnity version
.NET 7.xC# 11
.NET 6.xC# 10
.NET 5.xC# 9.02021.2
.NET Core 3.xC# 8.02020.2
.NET Framework 4.0Task.Factory.StartNew
.NET Framework 1.1Thread

TaskFactory Task Unwrap, ContinueWith

generated: IAsyncStateMachine AsyncMethodBuilder CancellationToken

Task

  • Unwrap
    • Creates a proxy Task
public static System.Threading.Tasks.Task Unwrap (this System.Threading.Tasks.Task<System.Threading.Tasks.Task> task);
Task<Task<int>> barMarker = Bar();
Task<int> awaitedMarker = await barMarker;
Task<int> unwrappedMarker = barMarker.Unwrap();
  • ContinueWith

    • Creates a continuation that executes asynchronously when the target Task completes.
  • Task.Run하고 Task.Factory.StartNew

    • Task.Run (someAction);
      • Task.InternalStartNew
    • Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
      • Task.InternalStartNew

TaskCreationOptions.DenyChildAttach

TaskCreationOptions (Flag)설명
None기본 동작을 사용함.특별한 요구사항이 없는 일반적인 경우
PreferFairness작업이 큐에 추가된 순서대로 실행되도록 함. 작업의 실행 순서가 중요한 경우
LongRunning장기 실행 작업임을 나타냅니다. 오래 실행되는 작업(예: 파일 I/O, 네트워크 작업)을 처리할 때
AttachedToParent작업을 부모 작업에 연결함. 부모 작업이 완료되기 전에 자식 작업이 완료되어야 하는 경우
DenyChildAttach자식 작업이 부모에 연결되는 것을 방지함. 독립적인 작업 실행이 필요한 경우
HideScheduler작업 내에서 생성된 새 작업이 현재 작업의 스케줄러를 상속받지 않고 기본 스케줄러를 사용해야 할 때 유용
RunContinuationsAsynchronously연속 작업(continuation)이 현재 스레드를 차단하지 않고 비동기적으로 실행되어야 할 때 사용함. 데드락을 방지하는 데 도움이 될 수 있음.
public Task StartNew(Action action)
{
    Task? currTask = Task.InternalCurrent;
    return Task.InternalStartNew(
        currTask,
        action,
        null,
        m_defaultCancellationToken,
        GetDefaultScheduler(currTask),
        m_defaultCreationOptions,
        InternalTaskOptions.None);
}

internal static Task InternalStartNew(
        Task? creatingTask,
        Delegate action
        object? state,
        CancellationToken cancellationToken,
        TaskScheduler scheduler,
        TaskCreationOptions options,
        InternalTaskOptions internalOptions)
CategoryDetached child tasksAttached child tasks
Parent waits for child tasks to complete.NoYes
Parent propagates exceptions thrown by child tasks.NoYes
Status of parent depends on status of child.NoYes

CancellationTokenSource & CancellationToken

  • CancellationTokenSource : IDisposable

    • Cancel()을 호출하여 취소 신호를 보내는 역할
      • Cancel을 여러 번 호출이 안전하며, 상태가 변경되지 않은 상태로 남아 있음.
    • Dispose전 Cancel도 해주자
    • CancellationTokenSource.Token => CancellationToken
  • CancellationToken:

    • 실제로 취소가 발생했는지를 감지할 수 있는 수동적인 토큰.
    • 콜백이 이미 실행 중이거나 완료된 경우 다시 실행되지 않음.
    • IsCancellationRequested: 현재 취소 요청 여부를 확인할 수 있음.
    • ThrowIfCancellationRequested: OperationCanceledException 예외 발생
  • https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.cancellationtoken?view=net-9.0

// 토큰은 합칠 수 도 있다.
CancellationToken combinedToken = CancellationTokenSource.CreateLinkedTokenSource(token1, token2).Token;

SynchronizationContext

  • SynchronizationContext
    • thread동기화할때 사용하면 좋다

public static System.Threading.SynchronizationContext? Current { get; }

SynchronizationContext.Current
Unity MainThread (UnityApiThread)UnityEngine.UnitySynchronizationContext
Other Threadnull
  • ex
    • WPF - DispatcherSynchronizationContext
    • MAUI - DispatcherQueueSynchronizationContext
    • ASP.NET AspNetSynchronizationContext
Thread thread = Thread.CurrentThread;
int threadId = thread.ManagedThreadId;
int threadId = Thread.CurrentThread.ManagedThreadId;
int processorId = Thread.GetCurrentProcessorId();

SynchronizationContext context = SynchronizationContext.Current;

TaskScheduler

  • TaskScheduler.FromCurrentSynchronizationContext
    • UI쓰레드등. task 동기화할때 사용하면 좋다
    • 메인쓰레드(유니티)
TaskScheduler설명
Default기본 스케줄러로, 스레드 풀을 사용함. 일반적인 비동기 작업 실행 시
Current현재 실행 중인 작업의 스케줄러를 반환함. 현재 컨텍스트에서 작업을 계속 실행해야 할 때
FromCurrentSynchronizationContext현재 동기화 컨텍스트에서 작업을 실행함. UI 스레드에서 작업을 실행해야 할 때 (WPF, WinForms 등)
SynchronizationContextCurrent 현재 동기화 컨텍스트를 사용함. UI스레드와의동기화가 필요한 경우
ConcurrentExclusiveSchedulerPair동시성과 배타성을 제어하는 스케줄러 쌍을 생성함. 리소스 접근을 세밀하게 제어해야 할 때
LimitedConcurrencyLevelTaskScheduler동시 실행 작업 수를 제한함.리소스 사용을 제한하거나 동시성을 제어해야 할 때
TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();
TaskScheduler scheduler2 = TaskScheduler.Current;

namespace System.Threading.Tasks
{
    public abstract class TaskScheduler
    {
      public static TaskScheduler Current => InternalCurrent ?? Default;
      internal static TaskScheduler? InternalCurrent
      {
          get
          {
              Task? currentTask = Task.InternalCurrent;
          }
      }
      public static TaskScheduler FromCurrentSynchronizationContext()
      {
          return new SynchronizationContextTaskScheduler();
      }
    }
}
class Program
{
    static void Main()
    {
        ProcessThread thread = GetCurrentThread();
        Console.WriteLine($"현재 스레드의 ID: {thread.Id}");
        Console.WriteLine($"현재 스레드가 동작하는 코어: {GetProcessorAffinity(thread)}");
    }

    static ProcessThread GetCurrentThread()
    {
        int threadId = Thread.CurrentThread.ManagedThreadId;
        Process currentProcess = Process.GetCurrentProcess();
        return currentProcess.Threads.Cast<ProcessThread>().FirstOrDefault(t => t.Id == threadId);
    }

    static int GetProcessorAffinity(ProcessThread thread)
    {
        return (int)thread.ProcessorAffinity;
    }
}

SynchronizationContext
ctx.Sendsync
ctx.Postasync

ConfigureAwait

// System.Runtime.CompilerServices

public ConfiguredTaskAwaitable ConfigureAwait (bool continueOnCapturedContext);
continueOnCapturedContextawait 이후의 코드를 SynchronizationContext에 이용
true - 기본값await 이후 현재 쓰레드에서 실행
falseawait 이후 새로운 쓰레드에서 실행

Awaiter

public TaskAwaiter<bool> GetAwaiter()
{
    return _downloadAllTask.GetAwaiter();
}

public bool IsCompleted
{
    get
    {
    }
}

public void GetResult() { }

INotifyCompletion, ICriticalNotifyCompletion

// INotifyCompletion
void OnCompleted(Action continuation)
{
}
// ICriticalNotifyCompletion : System.Runtime.CompilerServices.INotifyCompletion

public void OnCompleted (Action continuation)
{
}
public void UnsafeOnCompleted (Action continuation)
{
}

ValueTask

public async Task<bool> MoveNextAsync()
{
    if (_bufferdCount == 0)
    {
        await FillBuffer();    // 새로운 Task<bool> 객체를 할당
    }
    return _bufferedCount > 0; // (true와 false), 오직 2개의 Task<bool> 객체만 필요.
}
private readonly Dictionary<int, string> _cache = new Dictionary<int, string>();
public async ValueTask<string> GetCachedDataAsync(int id)
{
    if (_cache.TryGetValue(id, out string cachedData))
    {
        // 동기적으로 바로 반환 가능할 때, ValueTask가 메모리 할당을 줄여줌
        return cachedData;
    }

    // 없으면, 비동기 작업으로 데이터를 가져옴
    string data = await FetchDataFromDatabaseAsync(id);
    _cache[id] = data;
    return data;
}

private async Task<string> FetchDataFromDatabaseAsync(int id)
{
    await Task.Delay(100); // 데이터베이스에서 가져온다고 가정
    return $"Data_{id}";
}
  • Task는 Class.
    • Task같은경우 경우의 수가 많기에, 보다 적은수의 캐싱을 유지
    • 완료될 때까지 대기할 수 있으며, 실행이 완료되면 IsCompleted, Result, GetAwaiter().GetResult()와 같은 API가 작업을 완료 상태로 유지.
    • 여러 스레드에서 안전하게 접근할 수 있도록 설계됨.
  • ValueTask는 Struct
    • 캐싱 걱정 없음
    • 기본적으로 싱글 컨슈머(single-consumer) 패턴을 전제로 하여 가볍게 설계됨.
    • ValueTask는 IValueTaskSource라는 인터페이스를 사용해 동작할 수 있으며, 메모리 할당을 최소화하는 방식으로 작동.
      • 이로 인해 ValueTask는 여러 스레드에서 동시에 접근하거나 두 번 이상 await하는 경우 문제가 발생할 수 있다.
상황Task / TaskValueTask / ValueTask
여러 번 await할 때완료된 후에도 다시 불완료 상태로 전환되지 않으므로 여러 번 await해도 항상 동일한 결과를 얻을 수 있음.객체가 이미 다른 작업에서 재사용되고 있을 수 있어, 여러 번 await할 경우 문제가 발생할 수 있음.
동시에 await할 때안정적으로 사용할 수 있음.하나의 콜백만을 하나의 소비자와 함께 사용하도록 설계되었으므로, 동시 await 시 경쟁 상태와 같은 오류가 발생할 수 있음.
GetAwaiter().GetResult() 호출 시작업이 완료될 때까지 호출자를 차단, 작업 완료 전에도 안전하게 사용할 수 있음.작업이 완료되지 않았을 때 호출하면 대기하지 않을 수 있어, 경쟁 상태가 발생하고 의도한 대로 동작하지 않을 가능성이 큽니다. IValueTaskSource 구현체는 지원을 보장하지 않음.
  • 메모리 효율성: 데이터가 캐시에 있을 경우, 동기적으로 완료되므로 Task 객체를 생성하지 않아 메모리 할당이 줄어듬.
  • 메모리 할당 감소 효과: ValueTask는 주로 결과가 이미 완료된 작업이나 반복적인, 짧은 작업에서 메모리 할당을 줄이기 위해 사용됨.
  • 복잡한 에러 처리 및 호출 방식: ValueTask를 사용할 때는 한 번만 await 가능하거나 단일 소비자 패턴을 따라야 하는 제약 사항이 있는데, 이 경우 ComputeFromStreamAsync를 호출할 코드가 많아질 수 있음. Task는 안전하게 여러 번 await할 수 있어 더 관리가 용이함.
  • 동기 완료 가능성: ValueTask는 동기적으로 완료될 가능성이 높은 경우에 적합함. 이 메서드는 비동기적으로 스트림을 읽는 작업이므로 동기 완료 가능성이 거의 없어, ValueTask가 적절하지 않음.

Parallel

  • Task task = Task.Run(() => Parallel.ForEach(
  • await Parallel.ForEachAsync
    • System.Threading.Tasks.Parallel.dll

TLS

[ThreadStatic] 

ThreadLocal<T>

AsyncLocal<T>

ExecutionContext.SuppressFlow()

Ref

.tt

  • TextTransform .exe

    • https://learn.microsoft.com/ko-kr/visualstudio/modeling/generating-files-with-the-texttransform-utility?view=vs-2022
    • \Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE
    • \Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE
    • \Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE
  • T4 Text Template Transformation Toolkit

https://learn.microsoft.com/ko-kr/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2022

package System.CodeDom

Custom Tool
Runtime Text TempalteTextTemplatingFilePreprocessorC#에서 템플릿 파일명을 클래스명으로 접근 가능
Text TemplateTextTemplatingFileGenerator

T4를 이용한 INotifyPropertyChanged 코드 자동 생성

Liquid https://shopify.github.io/liquid/ https://github.com/lekman/AzureLiquid

<!-- 
Ref: https://learn.microsoft.com/en-us/visualstudio/msbuild/item-element-msbuild?view=vs-2022#attributes

-->

  <ItemGroup>
  
    <None Update="TextTemplate1.tt">
      <Generator>TextTemplatingFileGenerator</Generator>
      <LastGenOutput>TextTemplate1.txt</LastGenOutput>
    </None>
    <None Update="TextTemplate1.txt">
      <DesignTime>True</DesignTime>
      <AutoGen>True</AutoGen>
      <DependentUpon>TextTemplate1.tt</DependentUpon>
    </None>
  </ItemGroup>

  <ItemGroup>
    <Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
  </ItemGroup>
TextTemplatingFilePreprocessor RuntimeTextTemplate1.cs
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
True True RuntimeTextTemplate1.tt ## 디버깅
  • Solution Explorer > .tt > 우클릭 > Debug T4 Template
  • 혹은
<#@ template debug="true" hostSpecific="true" #>
<#@ import namespace="System.Diagnostics" #>
<# Debugger.Launch(); #>

DBCSCodePageEncoding은 Double Byte Character Set (DBCS) 코드 페이지 인코딩을

<#@ DirectiveName [AttributeName = "AttributeValue"] ... #> <# Standard control blocks #> 문장을 포함할 수 있습니다. <#= Expression control blocks # >표현식을 포함할 수 있습니다. <#+ Class feature control blocks #>메서드, 필드, 속성을 포함할 수 있습니다.

https://marketplace.visualstudio.com/items?itemName=bricelam.T4Language https://github.com/bricelam/T4Language

https://marketplace.visualstudio.com/items?itemName=bricelam.VSDataSqlite https://github.com/bricelam/VS.Data.Sqlite

<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".cs" #>

hostspecific="true": 템플릿에서 호스트 애플리케이션의 환경 정보, 파일 경로 등 호스트와 관련된 정보를 사용할 수 있습니다. // ex) 템플릿이 실행되는 디렉터리 경로를 가져옴

Version

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history

9.0

top level statement

Tool

$ dotnet new tool-manifest
$ cat .config/dotnet-tools.json

$ dotnet tool install dotnet-format
$ cat .config/dotnet-tools.json
  • dotnet-format
  • coverlet.console
  • dotnet-reportgenerator-globaltool
  • microsoft.dotnet.xharness.cli
  • microsoft.visualstudio.slngen.tool

Ref

SourceGenerator

Analyzer

Ref