성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>HttpContextAccessor를 통해 이해하는 AsyncLocal<T></h1> <p> HttpContext.Current가 동기 모델에 적합했다면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > HttpContext.Current를 통해 이해하는 CallContext와 ExecutionContext ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1608'>https://www.sysnet.pe.kr/2/0/1608</a> </pre> <br /> ASP.NET Core의 HttpContextAccessor는 비동기 모델을 위해 새롭게 나온 타입입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ASP.NET의 HttpContext.Current 구현에 대응하는 ASP.NET Core의 IHttpContextAccessor/HttpContextAccessor 사용법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11440'>https://www.sysnet.pe.kr/2/0/11440</a> </pre> <br /> 그리고, HttpContextAccessor는 내부적으로 AsyncLocal<T>의 래퍼 클래스에 불과합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > AsyncLocal<T> Class ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.threading.asynclocal-1'>https://learn.microsoft.com/en-us/dotnet/api/system.threading.asynclocal-1</a> [API Proposal]: Api handle Activity.Current value changes ; <a target='tab' href='[API Proposal]: Api handle Activity.Current value changes'>[API Proposal]: Api handle Activity.Current value changes</a> ; <a target='tab' href='https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-4/#added-new-tar-apis#observability'>https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-4/#added-new-tar-apis#observability</a> </pre> <br /> 실제로 .NET Reflector 등의 도구로 HttpContextAccessor를 보면 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\3.1.8\Microsoft.AspNetCore.Http.dll using System; using System.Threading; namespace Microsoft.AspNetCore.Http { // Token: 0x0200000F RID: 15 public class HttpContextAccessor : IHttpContextAccessor { public HttpContext HttpContext { get { HttpContextAccessor.HttpContextHolder value = HttpContextAccessor._httpContextCurrent.Value; if (value == null) { return null; } return value.Context; } set { HttpContextAccessor.HttpContextHolder value2 = HttpContextAccessor._httpContextCurrent.Value; if (value2 != null) { value2.Context = null; } if (value != null) { HttpContextAccessor._httpContextCurrent.Value = new HttpContextAccessor.HttpContextHolder { Context = value }; } } } <span style='color: blue; font-weight: bold'>private static AsyncLocal<HttpContextAccessor.HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextAccessor.HttpContextHolder>();</span> private class HttpContextHolder { public HttpContext Context; } } public sealed class DefaultHttpContext : HttpContext { // ...[생략]... } public abstract class HttpContext { // ...[생략]... } } </pre> <br /> 스레드를 넘나드는 정보를 다루기 때문에 당연히 AsyncLocal은 내부적으로 ExecutionContext의 처리 과정을 래핑합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace System.Threading { // Token: 0x020001F1 RID: 497 [NullableContext(1)] [Nullable(0)] public sealed class AsyncLocal<[Nullable(2)] T> : IAsyncLocal { // Token: 0x06001DED RID: 7661 RVA: 0x000A8969 File Offset: 0x000A7769 public AsyncLocal() { } // Token: 0x06001DEE RID: 7662 RVA: 0x00118FF7 File Offset: 0x00117DF7 public AsyncLocal([Nullable(new byte[] { 2, 0, 1 })] Action<AsyncLocalValueChangedArgs<T>> valueChangedHandler) { this.m_valueChangedHandler = valueChangedHandler; } // Token: 0x17000663 RID: 1635 // (get) Token: 0x06001DEF RID: 7663 RVA: 0x00119008 File Offset: 0x00117E08 // (set) Token: 0x06001DF0 RID: 7664 RVA: 0x0011902F File Offset: 0x00117E2F public T Value { [return: MaybeNull] get { object localValue = <span style='color: blue; font-weight: bold'>ExecutionContext.GetLocalValue(this);</span> if (localValue != null) { return (T)((object)localValue); } return default(T); } set { <span style='color: blue; font-weight: bold'>ExecutionContext.SetLocalValue(this, value, this.m_valueChangedHandler != null);</span> } } // Token: 0x06001DF1 RID: 7665 RVA: 0x00119048 File Offset: 0x00117E48 void IAsyncLocal.OnValueChanged(object previousValueObj, object currentValueObj, bool contextChanged) { T previousValue = (previousValueObj == null) ? default(T) : ((T)((object)previousValueObj)); T currentValue = (currentValueObj == null) ? default(T) : ((T)((object)currentValueObj)); this.m_valueChangedHandler(new AsyncLocalValueChangedArgs<T>(previousValue, currentValue, contextChanged)); } // Token: 0x0400070A RID: 1802 private readonly Action<AsyncLocalValueChangedArgs<T>> m_valueChangedHandler; } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 따라서, 우리도 HttpContextAccessor처럼 AsyncLocal<T>를 사용해 스레드 간의 문맥 정보 전달을 할 수 있습니다. 다음은 이것을 테스트한 간단한 예제 코드입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Threading; using System.Threading.Tasks; namespace Context1 { public class MyRefType { public string Name; public int Age; public override string ToString() { return $"{Name}: {Age}"; } } class Program { <span style='color: blue; font-weight: bold'>static AsyncLocal<string> s_asyncText = new AsyncLocal<string>(); static AsyncLocal<int> s_asyncInt = new AsyncLocal<int>(); static AsyncLocal<MyRefType> s_asyncRef = new AsyncLocal<MyRefType>();</span> static async Task Main(string[] args) { int count = 3; <span style='color: blue; font-weight: bold'>s_asyncRef.Value</span> = new MyRefType { Name = $"User#{count}", Age = count }; while (count-- > 0) { <span style='color: blue; font-weight: bold'>s_asyncText.Value</span> = $"TEST#{count}"; <span style='color: blue; font-weight: bold'>s_asyncInt.Value</span> = count; await AsyncMethodFirst(); OutputAsyncContext("AsyncMethodFirst - step4"); ThreadPool.QueueUserWorkItem((obj) => { OutputAsyncContext("QueueUserWorkItem"); }); ThreadPool.UnsafeQueueUserWorkItem((obj) => { OutputAsyncContext("UnsafeQueueUserWorkItem"); }, null); Thread t = new Thread(() => { OutputAsyncContext("new Thread"); }); t.Start(); Console.WriteLine(); <span style='color: blue; font-weight: bold'>s_asyncRef.Value</span> = null; } Console.ReadLine(); } private static async Task AsyncMethodFirst() { OutputAsyncContext("AsyncMethodFirst - step1"); await Task.Delay(1000); OutputAsyncContext("AsyncMethodFirst - step2"); await Task.Delay(1000); OutputAsyncContext("AsyncMethodFirst - step3"); await Task.Factory.StartNew(() => { OutputAsyncContext("Task.Factory.StartNew"); }); } private static void OutputAsyncContext(string title) { Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] {title}: {s_asyncText.Value} {s_asyncInt.Value}, {s_asyncRef.Value}"); } } } /* 출력 결과 [1] AsyncMethodFirst - step1: TEST#2 2, User#3: 3 [4] AsyncMethodFirst - step2: TEST#2 2, User#3: 3 [4] AsyncMethodFirst - step3: TEST#2 2, User#3: 3 [4] Task.Factory.StartNew: TEST#2 2, User#3: 3 [4] AsyncMethodFirst - step4: TEST#2 2, User#3: 3 [5] QueueUserWorkItem: TEST#2 2, User#3: 3 [6] UnsafeQueueUserWorkItem: 0, [4] AsyncMethodFirst - step1: TEST#1 1, [8] new Thread: TEST#2 2, User#3: 3 [7] AsyncMethodFirst - step2: TEST#1 1, [5] AsyncMethodFirst - step3: TEST#1 1, [5] Task.Factory.StartNew: TEST#1 1, [5] AsyncMethodFirst - step4: TEST#1 1, [7] UnsafeQueueUserWorkItem: 0, [4] QueueUserWorkItem: TEST#1 1, [5] AsyncMethodFirst - step1: TEST#0 0, [9] new Thread: TEST#1 1, [6] AsyncMethodFirst - step2: TEST#0 0, [4] AsyncMethodFirst - step3: TEST#0 0, [7] Task.Factory.StartNew: TEST#0 0, [7] AsyncMethodFirst - step4: TEST#0 0, [5] UnsafeQueueUserWorkItem: 0, [4] QueueUserWorkItem: TEST#0 0, [10] new Thread: TEST#0 0, */ </pre> <br /> QueueUserWorkItem, UnsafeQueueUserWorkItem과 Thread에서 보이는 결과에 따르면, AsyncLocal은 LogicalCallContext로 구현된 듯합니다. .NET 초기 시절에, 과연 LogicalCallContext가 향후 async/await에서 유용하게 사용할 거라는 것을 누가 예상했을까요? ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1688&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6470
(왼쪽의 숫자를 입력해야 합니다.)