성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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'>(번역글) .NET Internals Cookbook Part 7 - Word tearing, locking and others</h1> <p> <br /> 이번에도 .NET Internals Cookbook 시리즈의 7번째 글을 번역한 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET Internals Cookbook Part 7 - Word tearing, locking and others ; <a target='tab' href='https://blog.adamfurmanek.pl/2019/03/30/net-internals-cookbook-part-7/'>https://blog.adamfurmanek.pl/2019/03/30/net-internals-cookbook-part-7/</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>45. 그래도 박싱(boxing)이 필요한 경우?</div> <br /> 박싱은 스택의 데이터를 힙에 중복해서 올리기 때문에 분명히 성능상 좋은 작업은 아닙니다. 하지만, 필드를 많이 가진 값 형식의 인스턴스를 다룰 때 값의 치환을 스레드에 안정적으로 다루는 용도로 사용할 수 있습니다.<br /> <br /> 가령 다음과 같은 struct를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > struct Foo { public int A { get; set; } public int B { get; set; } public int C { get; set; } public int D { get; set; } public int E { get; set; } } </pre> <br /> 보유한 클래스가 있다고 가정해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class Test { Foo _foo = new Foo(); public void ChangeFoo(Foo foo) { _foo = foo; } public void WriteValues() { Console.WriteLine(_foo.A); Console.WriteLine(_foo.E); } } </pre> <br /> 위에서 _foo = foo 대입문은 5개의 int 필드에 대한 값 복사가 발생합니다. 즉, 코드는 한 줄이지만 실제로는 다음과 같은 코드가 실행되는 것과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > _foo.A = foo.A; _foo.B = foo.B; _foo.C = foo.C; _foo.D = foo.D; _foo.E = foo.E; </pre> <br /> 일반적인 경우 저런 코드가 문제 되지 않지만 만약 다중 스레드 상황으로 바뀌면 의도치 않은 버그가 발생할 수 있습니다. 가령 A 스레드가 foo 구조체의 A, B까지 값을 대입한 상황에서 B 스레드가 WriteValues 메서드를 호출해 A와 E 멤버를 접근하면 foo가 _foo에 완전하게 대입되기도 전에 값을 접근하는 결과가 됩니다.<br /> <br /> 저런 경우를 방지하기 위해서는 Foo를 class로 바꾸고 다음과 같은 식으로 코딩하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class Test { Foo _foo = new Foo(); public void ChangeFoo(Foo foo) { _foo = foo; } public void WriteValues() { Foo temp = _foo; Console.WriteLine(temp.A); Console.WriteLine(temp.E); } } </pre> <br /> 참조 형식의 경우 GC Heap을 가리키는 주솟값(x86 4바이트, x64 8바이트)에 불과하기 때문에 대입 과정이 atomic하게 처리되기 때문입니다.<br /> <br /> 또는, class로 바꾸지 않고 의도적으로 필요한 순간에만 박싱을 해 주솟값 대입으로 변경하는 방법도 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class Test { object _foo = new Foo(); public void ChangeFoo(Foo foo) { _foo = foo; } public void WriteValues() { object obj = _foo; Foo temp = (Foo)obj; Console.WriteLine(temp.A); Console.WriteLine(temp.E); } } </pre> <br /> 위와 같은 특징을 감안하고 원 글(<a target='tab' href='https://blog.adamfurmanek.pl/2019/03/30/net-internals-cookbook-part-7/'>.NET Internals Cookbook Part 7 ? Word tearing, locking and others</a>)의 예제 코드를 보면,<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.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; public class Program { public static void Main() { var source = Enumerable.Range(0, 1000).ToArray(); while(true) { Foo result = UnsafeParallel(source, n => { Thread.Sleep(0); return new Foo { A = n, B = n, C = n, D = n, E = n }; }); if (result.A != result.B || result.A != result.C || result.A != result.D || result.A != result.E) { Console.WriteLine("Tearing detected!"); Console.WriteLine(result.A); Console.WriteLine(result.B); Console.WriteLine(result.C); Console.WriteLine(result.D); Console.WriteLine(result.E); break; } } } static T UnsafeParallel(IEnumerable source, Func action) { T result = default(T); Parallel.ForEach(source, (i, state) => { result = action(i); state.Stop(); }); return result; } static T SafeParallel(IEnumerable source, Func action) { object result = default(T); Parallel.ForEach(source, (i, state) => { result = action(i); state.Stop(); }); return (T)result; } } </pre> <br /> 왜 UnsafeParallel에서는 문제가 발생하고, SafeParallel에서는 문제가 발생하지 않는지 이해할 수 있을 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>46. 다중 연결된 delegate의 경우 중간 호출에서 예외가 발생한다면?</div> <br /> 예를 들어 다음의 코드를 보면,<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; public class Program { delegate void Foo(); public static void Main() { Foo foo = () => Console.WriteLine("First"); foo += () => throw new Exception("Second"); foo += () => Console.WriteLine("Third"); try { foo(); } catch (Exception e) { Console.WriteLine(e); } } } /* 출력 결과 First System.Exception: Second at Program.<>c.<Main>b__1_1() in C:\temp\cookbook_series7_sample\q46\ConsoleApp1\Program.cs:line 9 at Program.Foo.Invoke() at Program.Main() in C:\temp\cookbook_series7_sample\q46\ConsoleApp1\Program.cs:line 14 */ </pre> <br /> 두 번째 연결된 delegate 호출에서 예외가 발생하므로 그 이후의 콜백 호출이 안 되는 것을 볼 수 있습니다. 만약 예외를 감안해 모든 delegate를 호출하고 싶다면 다음의 글에서 설명한,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 올바른 이벤트 예외 정보 출력 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/620'>http://www.sysnet.pe.kr/2/0/620</a> </pre> <br /> GetInvocationList 메서드를 이용해 하나씩 try/catch로 감쌀 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /><a name="tag47"></a> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>47. foreach 문을 사용하려면 IEnumerator/IEnumerable 인터페이스를 반드시 구현해야 할까?</div> <br /> 다음의 코드에서 보듯이,<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; public class Program { public static void Main() { var bar = new Bar(); foreach (int item in bar) { Console.WriteLine(item); } } } class Foo { public <span style='color: blue; font-weight: bold'>int Current</span> { get; private set; } private int step; public <span style='color: blue; font-weight: bold'>bool MoveNext</span>() { if (step >= 5) return false; Current = step++; return true; } } class Bar { public Foo <span style='color: blue; font-weight: bold'>GetEnumerator</span>() { return new Foo(); } } </pre> <br /> IEnumerator/IEnumerable 인터페이스를 굳이 명시하지 않아도 관련 속성과 메서드를 가지는 것만으로 C# 컴파일러는 foreach에 해당 멤버들을 사용해 코드를 생성해 줍니다.<br /> <br /> 이 규칙은 비동기 호출에 관련된 await에도 적용되는데 다음의 코드를 보면 정숫값(2000)을 await할 수 있도록 확장 메서드를 연결하고 있습니다.<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.Runtime.CompilerServices; using System.Threading.Tasks; namespace AwaitOnInteger { class Program { static void Main(string[] args) { WaitForInt().Wait(); } static async Task WaitForInt() { Console.WriteLine($"Waiting starting at {DateTime.Now}"); <span style='color: blue; font-weight: bold'>await 2000;</span> Console.WriteLine($"Waiting finished at {DateTime.Now}"); } } public static class AwaitableInt { public static <span style='color: blue; font-weight: bold'>TaskAwaiter GetAwaiter</span>(<span style='color: blue; font-weight: bold'>this int miliseconds</span>) { return Task.Delay(TimeSpan.FromMilliseconds(miliseconds)).GetAwaiter(); } } } </pre> <br /> 위의 규칙에 대해서는 저도 예전에 ^^ 설명한 적이 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - await을 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11456'>http://www.sysnet.pe.kr/2/0/11456</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>48. LINQ 쿼리의 동작과 컴파일 방식</div> <br /> SQL 쿼리 구문처럼 보이는 LINQ는,<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.Linq; namespace Program { public class Program { public static void Main(string[] args) { var source = Enumerable.Range(0, 100); var filtered = <span style='color: blue; font-weight: bold'>from i in source where i % 3 == 0 select i / 2</span>; } } } </pre> <br /> 빌드하면 결국 관련된 IEnumerable의 확장 메서드로 번역이 된다는 것을 .NET Reflector 도구 등으로 확인해 보면 알 수 있습니다.<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.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; namespace Program { public class Program { [CompilerGenerated] [Serializable] private sealed class <>c { public static readonly Program.<>c <>9 = new Program.<>c(); public static Func <>9__0_0; public static Func <>9__0_1; internal bool b__0_0(int i) { return i % 3 == 0; } internal int b__0_1(int i) { return i / 2; } } public static void Main(string[] args) { IEnumerable enumerable = Enumerable.Range(0, 100); IEnumerable arg_2A_0 = enumerable; Func arg_2A_1; if ((arg_2A_1 = Program.<>c.<>9__0_0) == null) { arg_2A_1 = (Program.<>c.<>9__0_0 = new Func(Program.<>c.<>9.b__0_0)); } IEnumerable arg_4E_0 = <span style='color: blue; font-weight: bold'>arg_2A_0.Where(arg_2A_1);</span> Func arg_4E_1; if ((arg_4E_1 = Program.<>c.<>9__0_1) == null) { arg_4E_1 = (Program.<>c.<>9__0_1 = new Func(Program.<>c.<>9.b__0_1)); } IEnumerable enumerable2 = <span style='color: blue; font-weight: bold'>arg_4E_0.Select(arg_4E_1);</span> } } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>49. IEnumerable.Select와 IQueryable.Select의 차이점</div> <br /> 메서드 시그니처를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [IEnumerable.Select] public static System.Collections.Generic.IEnumerable Select (this System.Collections.Generic.IEnumerable source, Func selector); [IQueryable.Select] public static System.Linq.IQueryable Select (this System.Linq.IQueryable source, System.Linq.Expressions.Expression> selector); </pre> <br /> IEnumerable 측은 람다를 코드(Func)로 받아들여 실행시키는 반면 IQueryable의 경우 람다를 데이터(System.Linq.Expressions.Expression)로 받아들여 분석해 실행 시 SQL 쿼리로 번역합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>50. Reflection으로 private 멤버를 접근하지 못하도록 막는 방법</div> <br /> .NET 4.6/.NET Core 1.0부터 제공되는 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.disableprivatereflectionattribute'>DisablePrivateReflectionAttribute</a> 특성을 어셈블리 수준에 부여하거나,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [assembly: DisablePrivateReflection] </pre> <br /> <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.security.permissions.reflectionpermission'>ReflectionPermission</a>을 사용하면 된다고 합니다.<br /> <br /> 그런데, 실제로 해보면 .NET Framework 응용 프로그램에서는 동작하지 않고, .NET Core에서만 동작합니다. 예를 들어 다음의 코드를 .NET Standard 라이브러리로 만들고,<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.Runtime.CompilerServices; <span style='color: blue; font-weight: bold'>[assembly: DisablePrivateReflection]</span> public class Class1 { <span style='color: blue; font-weight: bold'>private int i</span> = 5; public void Print() { Console.WriteLine(i); } } </pre> <br /> .NET Core 콘솔에서 다음과 같이 Class1을 사용하면,<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.Reflection; class Program { static void Main(string[] args) { Class1 cl = new Class1(); cl.Print(); SetPrivate(cl, 6); cl.Print(); } private static void SetPrivate(Class1 cl, int v) { Type type = cl.GetType(); FieldInfo fi = type.GetField("i", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (fi == null) { return; } fi.SetValue(cl, v); } } </pre> <br /> SetValue 메서드 수행에서 다음과 같은 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Unhandled Exception: System.FieldAccessException: Attempt by method 'Program.SetPrivate(Class1, Int32)' to access field 'Class1.i' failed. at System.Reflection.RtFieldInfo.PerformVisibilityCheckOnField(IntPtr field, Object target, RuntimeType declaringType, FieldAttributes attr, UInt32 invocationFlags) at System.Reflection.RtFieldInfo.InternalSetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture, StackCrawlMark& stackMark) at System.Reflection.RtFieldInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture) at System.Reflection.FieldInfo.SetValue(Object obj, Object value) at Program.SetPrivate(Class1 cl, Int32 v) in C:\temp\NetCoreCon1\Program.cs:line 25 at Program.Main(String[] args) in C:\temp\NetCoreCon1\Program.cs:line 11 </pre> <br /> 반면 동일한 코드를 .NET Framework 응용 프로그램에서 실행하면 예외가 발생하지 않습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>51. lock 구문에 값 형식(value type)의 인스턴스를 사용할 수 있을까?</div> <br /> 아래의 글에서도 설명했지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET 참조 개체 인스턴스의 Object Header를 확인하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1175'>https://www.sysnet.pe.kr/2/0/1175</a> </pre> <br /> 값 형식은 Object Header가 없기 때문에 lock 구문으로 사용할 수 없습니다. 그래서 다음과 같이 lock에 사용하려고 시도하면 컴파일 오류가 발생합니다.<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; public class Program { public static void Main(string[] args) { int i = 5; // 컴파일 에러 // error CS0185: 'int' is not a reference type as required by the lock statement lock(i) { } } } </pre> <br /> <a target='tab' href='http://www.sysnet.pe.kr/2/0/11873#tag32'>lock 구문이 실제로는 Monitor.Enter 코드로 변경된다는 것을 지난 글에서 설명</a>했는데요. 따라서 다음과 같이 풀어서 접근하는 것을 시도해 볼 수는 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>int i</span> = 5; bool wasTaken = false; try { <span style='color: blue; font-weight: bold'>Monitor.Enter(i</span>, ref wasTaken); } finally { if (wasTaken) { <span style='color: blue; font-weight: bold'>Monitor.Exit(i)</span>; } } </pre> <br /> 이번엔 컴파일 오류는 발생하지 않지만, 실행 시 다음과 같은 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Unhandled Exception: System.Threading.SynchronizationLockException: Object synchronization method was called from an unsynchronized block of code. </pre> <br /> 왜냐하면, Monitor.Enter의 첫 번째 인자는 object 타입이므로 int i는 object로 박싱되어 컴파일을 통과할 수 있었지만, Monitor.Exit에 사용된 i도 그 순간 새롭게 object로 박싱된 것이므로 Enter와 Exit에 사용된 객체가 같지 않기 때문입니다. 결국 Exit(i)의 박싱된 object는 Enter에 사용된 적이 없으므로 Exit에서 해제 시 오류가 발생하는 것입니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1441&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </h1><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1625
(왼쪽의 숫자를 입력해야 합니다.)