성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
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'>HttpContext.Current를 통해 이해하는 CallContext와 ExecutionContext</h1> <p> ASP.NET의 경우 HttpContext.Current를 사용해서 ASP.NET pipeline에서의 동일한 문맥 정보를 제공합니다. 일례로 요청을 처리하는 스레드에서 실행하는 모든 코드에서는 무조건 HttpContext.Current 속성을 이용하면 현재 문맥을 가져올 수 있습니다.<br /> <br /> 그런데, 도대체 HttpContext에 어떤 마법이 숨겨져 있는 걸까요? ^^ 이번엔 이에 대해서 간단하게(?) 파악해 보겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, 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;' > // System.Web.dll public sealed class HttpContext : IServiceProvider { // ...[생략]... public static HttpContext Current { get { return (ContextBase.Current as HttpContext); } set { ContextBase.Current = value; } } } </pre> <br /> 아하... ContextBase.Current를 형변환 한 것에 불과하군요. 이어서 ContextBase를 가보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // System.Web.dll internal class ContextBase { // ...[생략]... internal static object Current { get { return CallContext.HostContext; } set { CallContext.HostContext = value; } } } </pre> <br /> ContextBase.Current의 내부 구현은 결국 CallContext.HostContext임을 알 수 있습니다. 그리고 CallContext의 정적 공용 변수인 HostContext는 Thread마다 고유하게 할당된 ExecutionContext가 포함하고 있는 2개의 CallContext 인스턴스에 값을 읽고 쓰는 것에 불과합니다.<br /> <br /> 여기에서 CallContext.HostContext의 set을 살펴보면 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static void set_HostContext(object value) ExecutionContext mutableExecutionContext = Thread.CurrentThread.GetMutableExecutionContext(); if (value is ILogicalThreadAffinative) { mutableExecutionContext.<span style='color: blue; font-weight: bold'>IllogicalCallContext</span>.HostContext = null; mutableExecutionContext.<span style='color: blue; font-weight: bold'>LogicalCallContext</span>.HostContext = value; } else { mutableExecutionContext.<span style='color: blue; font-weight: bold'>IllogicalCallContext</span>.HostContext = value; mutableExecutionContext.<span style='color: blue; font-weight: bold'>LogicalCallContext</span>.HostContext = null; } } </pre> <a name='marshal'></a> <br /> 여기서 재미있는 점이 바로, ExecutionContext가 관리하는 2개의 CallContext입니다. 왜 2개일까요? 이는 객체의 Serializable과 관련이 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Identifying the differences between CallContext Data Slots ; <a target='tab' href='http://dotnetmustard.blogspot.kr/2008/08/identifying-differences-between.html'>http://dotnetmustard.blogspot.kr/2008/08/identifying-differences-between.html</a> </pre> <br /> Thread 객체가 소유한 (Hashtable로 데이터를 관리하는) CallContext의 데이터를 스레드 간에 넘길 것이냐 말 것이냐에 따라 어느 CallContext에 데이터가 get/set 되는지가 결정되기 때문입니다. (보다 정확하게는 Thread 간이라고 볼 수는 없습니다. 왜냐하면 CallContext가 정의된 네임스페이스는 System.Runtime.Remoting.Messaging이고 이는 원격 메서드 호출에 관여하기 때문입니다. 물론 원격 메서드 호출은 확실히 스레드가 달라지긴 하지만.)<br /> <br /> Thread가 가지고 있는 2개의 CallContext는 각기 2개의 클래스로 나뉘는데요. 하나는 LogicalCallContext이고, 다른 하나는 IllogicalCallContext입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > internal class LogicalCallContext : <span style='color: blue; font-weight: bold'>ISerializable</span>, ICloneable { // ...[생략]... private Hashtable m_Datastore; private object m_HostContext; } internal class IllogicalCallContext : ICloneable { // ...[생략]... private Hashtable m_Datastore; private object m_HostContext; } </pre> <br /> 전자가 직렬화 가능한 객체를 담고, 후자는 직렬화가 지원되지 않는 객체를 담습니다. 각각의 CallContext는 Thread 인스턴스에서 직접 접근할 수는 없고, CallContext의 정적 메서드를 사용해야만 가능합니다.<br /> <br /> <ul> <li>public static object GetData(string name);</li> <li>public static void SetData(string name, object data);</li> <li>public static object <span style='color: blue; font-weight: bold'>Logical</span>GetData(string name);</li> <li>public static void <span style='color: blue; font-weight: bold'>Logical</span>SetData(string name, object data);</li> </ul> <br /> 우선, LogicalSetData는 Thread.ExecutionContext.LogicalCallContext에 값을 set 합니다. 반면, SetData는 인자로 주어진 object data 인스턴스가 ILogicalThreadAffinative 인터페이스를 구현하고 있다면 LogicalCallContext로, 그렇지 않으면 IllogicalCallContext로 보관됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 말로만 설명하면 너무 식상하니 ^^ 코딩을 해보겠습니다. 우선, Thread의 LogicalCallContext에 값을 보관하려면 다음과 같이 해주면 됩니다.<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 <span style='color: blue; font-weight: bold'>LTAObject : ILogicalThreadAffinative</span> { public string Value { get; set; } } class Program { static void Main(string[] args) { // ILogicalThreadAffinative 인터페이스를 구현하지 않은 string 객체라도 // 명시적으로 LogicalCallContext에 값을 보관 <span style='color: blue; font-weight: bold'>CallContext.LogicalSetData("key1", "test");</span> Debug.Assert(CallContext.LogicalGetData("key1") as string == "test"); // ILogicalThreadAffinative 인터페이스를 상속받은 객체를 생성하고, <span style='color: blue; font-weight: bold'>LTAObject lta</span> = new LTAObject(); lta.Value = "test"; // LogicalCallContext에 값을 보관 <span style='color: blue; font-weight: bold'>CallContext.SetData("key1", lta);</span> Debug.Assert((CallContext.LogicalGetData("key1") as LTAObject).Value == "test"); } } </pre> <br /> 반면, Thread의 IllogicalCallContext에 값을 보관하는 코드는 다음과 같습니다.<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 NoLTAObject { public string Value { get; set; } } class Program { static void Main(string[] args) { // string 객체는 ILogicalThreadAffinative를 상속받지 않았으므로 IllogicalCallContext에 보관됨. <span style='color: blue; font-weight: bold'>CallContext.SetData("key1", "test");</span> Debug.Assert(CallContext.GetData("key1") as string == "test"); Debug.Assert(CallContext.LogicalGetData("key1") == null); // LogicalCallContext에 없음. // NoLTAObject 객체도 ILogicalThreadAffinative를 상속받지 않았으므로 IllogicalCallContext에 보관됨. <span style='color: blue; font-weight: bold'>NoLTAObject noLta</span> = new NoLTAObject(); noLta.Value = "test"; <span style='color: blue; font-weight: bold'>CallContext.SetData("key1", noLta);</span> Debug.Assert((CallContext.GetData("key1") as NoLTAObject).Value == "test"); Debug.Assert(CallContext.LogicalGetData("key1") == null); // LogicalCallContext에 없음. } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 단일 스레드로 특정 작업이 처리되는 상황에서는 LogicalCallContext와 IllogicalCallContext 중에서 어디에 값을 보관하느냐는 문제될 것이 없습니다. 하지만, 그 작업과 관련되어 여러 가지 스레드들이 관여하게 된다면 그 스레드들이 공통으로 공유해야 할 문맥 정보가 필요합니다.<br /> <br /> 위에서 구현되었던 LTAObject와 NoLTAObject가 스레드를 넘어가는 경우의 차이점을 아래의 코드로 직접 확인해 볼 수 있습니다.<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 <span style='color: blue; font-weight: bold'>LTAObject : ILogicalThreadAffinative</span> { public string Value { get; set; } } public class <span style='color: blue; font-weight: bold'>NoLTAObject</span> { public string Value { get; set; } } class Program { static void Main(string[] args) { // LogicalCallContext에 값을 보관 <span style='color: blue; font-weight: bold'>CallContext.LogicalSetData("key1", "test");</span> // IllogicalCallContext에 값을 보관 <span style='color: blue; font-weight: bold'>CallContext.SetData("key2", "test");</span> // ILogicalThreadAffinative 인터페이스를 상속받은 객체를 생성하고, <span style='color: blue; font-weight: bold'>LTAObject lta = new LTAObject();</span> lta.Value = "test"; // LogicalCallContext에 값을 보관 <span style='color: blue; font-weight: bold'>CallContext.SetData("key3", lta);</span> // NoLTAObject 객체도 ILogicalThreadAffinative를 상속받지 않았으므로 IllogicalCallContext에 보관됨. <span style='color: blue; font-weight: bold'>NoLTAObject noLta = new NoLTAObject();</span> noLta.Value = "test"; <span style='color: blue; font-weight: bold'>CallContext.SetData("key4", noLta);</span> Thread thread = new Thread(threadFunc); thread.Start(); thread.Join(); return; } static void threadFunc() { // LogicalCallContext에 보관되었던 데이터들은 스레드 경계를 넘어옴 Debug.Assert(<span style='color: blue; font-weight: bold'>CallContext.LogicalGetData</span>("key1") as string == "test"); Debug.Assert((<span style='color: blue; font-weight: bold'>CallContext.LogicalGetData</span>("key3") as LTAObject).Value == "test"); // IllogicalCallContext에 보관되었던 데이터들은 스레드 경계를 못 넘음. Debug.Assert(<span style='color: blue; font-weight: bold'>CallContext.GetData</span>("key2") == null); Debug.Assert(<span style='color: blue; font-weight: bold'>CallContext.GetData</span>("key4") == null); } } </pre> <br /> 보시는 것처럼, LogicalCallContext에 보관되었던 항목들은 스레드 간에 자연스럽게 데이터 이관이 되었지만, IllogicalCallContext에 보관되었던 항목들은 안되었습니다.<br /> <br /> 그런데, 혹시 LogicalCallContext의 데이터도 스레드 간에 넘어가지 못하도록 막고 싶지는 않을까요? 데이터를 스레드 간에 넘긴다는 것은 그런 작업이 이뤄졌기 때문에 가능한 것이고 이것이 복잡한 경우 자칫 성능에 영향을 미칠 수 있습니다. 굳이 CallContext의 데이터들이 필요치 않다면 이를 넘기는데 괜한 오버헤드를 가져갈 이유가 없습니다.<br /> <br /> 이를 위해 ExecutionContext의 SuppressFlow / RestoreFlow 정적 메서드를 이용하면 됩니다.<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'>ExecutionContext.SuppressFlow();</span> Thread thread = new Thread(threadFunc); thread.Start(); thread.Join(); <span style='color: blue; font-weight: bold'>ExecutionContext.RestoreFlow();</span> static void threadFunc() { // 모든 CallContext의 데이터들이 스레드 경계를 못 넘음 Debug.Assert(CallContext.LogicalGetData("key1") == null); Debug.Assert(CallContext.GetData("key2") == null); Debug.Assert(CallContext.LogicalGetData("key3") == null); Debug.Assert(CallContext.GetData("key4") == null); } </pre> <br /> LogicalCallContext와 IllogicalCallContext 인스턴스는 ExecutionContext에 포함되어 있고, 다시 ExecutionContext는 Thread에 포함되어 있습니다. 따라서 관리는 ExecutionContext 측에서 이뤄지는데, 기본 동작이 스레드 간에 LogicalCallContext를 자동으로 넘겨주는 역할을 하고 이를 막으려면 개발자가 수작업으로 SuppressFlow / RestoreFlow를 호출해 주어야 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <a name='unsafe_tp'></a> <br /> 여기까지 이해하셨으면, ThreadPool 타입의 QueueUserWorkItem과 UnsafeQueueUserWorkItem 메서드에서도 CallContxt에 대한 차이가 있다는 것을 자연스럽게 파악할 수 있습니다.<br /> <br /> 즉, QueueUserWorkItem은 LogicalCallContext 데이터를 스레드 풀에서 가져올 스레드에 설정해 주는 반면, UnsafeQueueUserWorkItem은 LogicalCallContext 데이터 이관을 명시적으로 하지 않는다는 차이가 있습니다. 위의 예제 코드와 비교해서 설명해 보면 UnsafeQueueUserWorkItem은 SuppressFlow / RestoreFlow를 호출한 스레드처럼 동작하는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { CallContext.LogicalSetData("key1", "test"); <span style='color: blue; font-weight: bold'> ThreadPool.QueueUserWorkItem(threadFunc, null); ThreadPool.UnsafeQueueUserWorkItem(threadFunc, null); </span> Console.ReadLine(); } static void threadFunc(object objContext) { object objValue = CallContext.GetData("key1"); Console.WriteLine(objValue ?? "(null)"); // QueueUserWorkItem인 경우 "test" 출력 // UnsafeQueueUserWorkItem인 경우 "(null)" } </pre> <br /> 실제로 테스트해 보면 QueueUserWorkItem인 경우일지라도 그 전에 SuppressFlow를 호출해 주면 마찬가지로 LogicalCallContext 데이터가 이관되지 않는 것을 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { CallContext.LogicalSetData("key1", "test"); <span style='color: blue; font-weight: bold'> ExecutionContext.SuppressFlow(); ThreadPool.QueueUserWorkItem(threadFunc, null); ExecutionContext.RestoreFlow(); </span> ThreadPool.UnsafeQueueUserWorkItem(threadFunc, null); Console.ReadLine(); } static void threadFunc(object objContext) { object objValue = CallContext.GetData("key1"); Console.WriteLine(objValue ?? "(null)"); // QueueUserWorkItem, UnsafeQueueUserWorkItem 모두 "(null)" } </pre> <br /> SuppressFlow 사용 중에 일부러 LogicalCallContext를 전달하고 싶다면 대신 다른 방법을 사용해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { CallContext.LogicalSetData("key1", "test"); <span style='color: blue; font-weight: bold'>ExecutionContext threadContext = ExecutionContext.Capture();</span> // 미리 capture 해두고, ExecutionContext.SuppressFlow(); // suppress를 해도, // ThreadPool.UnsafeQueueUserWorkItem(threadFunc, null); <span style='color: blue; font-weight: bold'>ExecutionContext.Run(threadContext, threadFunc, null);</span> // capture했던 내용으로 직접 실행하는 것도 가능 ExecutionContext.RestoreFlow(); Console.ReadLine(); } static void threadFunc(object objContext) { object objValue = CallContext.GetData("key1"); Console.WriteLine(objValue); // "test" 값을 출력 } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 비동기 호출의 경우, LogicalCallContext의 데이터 이관이 어떻게 되는지 살펴보는 것도 흥미로운데요. 아래의 글을 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Nested multithread operations tracing ; <a target='tab' href='http://stackoverflow.com/questions/2651327/nested-multithread-operations-tracing'>http://stackoverflow.com/questions/2651327/nested-multithread-operations-tracing</a> </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;' > static void Main(string[] args) { string key = "aaa"; EventWaitHandle asyncStarted = new AutoResetEvent(false); IAsyncResult r = null; <span style='color: blue; font-weight: bold'>CallContext.LogicalSetData</span>(key, "Root - op 0"); Console.WriteLine("Initial: {0}", CallContext.LogicalGetData(key)); Action a = () => { <span style='color: blue; font-weight: bold'>CallContext.LogicalSetData</span>(key, "Async - op 0"); asyncStarted.Set(); }; r = <span style='color: blue; font-weight: bold'>a.BeginInvoke</span>(null, null); asyncStarted.WaitOne(); Console.WriteLine("AsyncOp started: {0}", <span style='color: blue; font-weight: bold'>CallContext.LogicalGetData</span>(key)); <span style='color: blue; font-weight: bold'>CallContext.LogicalSetData</span>(key, "Root - op 1"); Console.WriteLine("Current changed: {0}", <span style='color: blue; font-weight: bold'>CallContext.LogicalGetData</span>(key)); <span style='color: blue; font-weight: bold'>a.EndInvoke</span>(r); Console.WriteLine("Async ended: {0}", <span style='color: blue; font-weight: bold'>CallContext.LogicalGetData</span>(key)); } // 출력 결과 Initial: Root - op 0 AsyncOp started: Root - op 0 Current changed: Root - op 1 Async ended: Async - op 0 </pre> <br /> 처음 Main 메서드의 시작에서 key == "aaa"로 CallContext.LogicalSetData에 값을 넣었는데요. 비동기 호출되는 delegate 메서드 내에서 "aaa" 키에 해당하는 LogicalCallContext의 값을 변경하지만, 외부에서는 그 시점에 변화를 알 수 없습니다. 실제로 비동기 메서드 내에서의 LogicalCallContext 저장소가 그것의 부모 스레드의 LogicalCallContext에 병합되는 시점은 EndInvoke이라는 것이 특이합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 이제 다시 최초의 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를 접근하면 null이 나옵니다. 왜 그런 걸까요? </pre> <br /> 간단합니다. 소스 코드를 살펴보면 HttpContext 개체는 ILogicalThreadAffinative 인터페이스를 구현하지 않았습니다. 또한 CallContext.LogicalSetData를 통해서 값을 설정하지도 않았기 때문에 HttpContext 인스턴스 자체는 Thread의 IllogicalCallContext 영역에 보관되어 있습니다.<br /> <br /> 따라서, ASP.NET Page 내에서 다음과 같이 별도의 스레드로 넘어가버리면 해당 스레드에서는 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;' > using System; using System.Web; using System.Threading; using System.Runtime.Remoting.Messaging; namespace WebApplication1 { public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { ThreadPool.QueueUserWorkItem(threadFunc, null); } void threadFunc(object objContext) { object httpContext = <span style='color: blue; font-weight: bold'>HttpContext.Current</span>; // == null object hostContxtObj = <span style='color: blue; font-weight: bold'>CallContext.HostContext</span>; // null } } } </pre> <br /> 애당초 이것이 가능하려면 마이크로소프트가 HttpContext를 직렬화 가능한 클래스로 만들었어야 합니다. 단지, 그렇게 만들지 않았던 것에는 몇 가지 타당한 이유가 있어 보입니다. 가령 HttpContext.Current에서는 Server, Request, Response 등의 객체를 노출시켜 주는데 그런 객체들을 모두 마샬링 가능한 타입으로 만드는 것은 그다지 효율적이지 않았을 수 있습니다. 그 외에도, 새롭게 생성된 스레드가 현재 요청이 처리된 후에 - 즉, HttpContext가 소멸된 이후에 - HttpContext.Current를 접근한다면 이미 모든 정보가 유효하지 않은 상태에서 그것을 접근하게 해주는 것도 자칫 복잡한 문제를 야기시킬 수 있습니다. 따라서 제 의견에도 HttpContext가 객체가 스레드 간에 넘나들지 않게 한 것은 옳은 결정으로 보입니다.<br /> <br /> 어쨌든 다른 스레드에서는 HttpContext.Current를 넘겨줄 수 없으므로 필요한 정보가 있다면 그것만을 별도로 취합해서 HttpContext가 아닌 CallContext를 직접 사용해서 넘겨주는 방법을 택해야 합니다.<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.Web; using System.Threading; using System.Runtime.Remoting.Messaging; namespace WebApplication1 { public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { CallContext.LogicalSetData("RequestURL", HttpContext.Current.Request.Url.ToString()); ThreadPool.QueueUserWorkItem(threadFunc, null); } void threadFunc(object objContext) { object obj1 = CallContext.LogicalGetData("RequestURL"); } } } </pre> <br /> 이 정도면 HttpContext.Current의 미스테리가 좀 풀렸겠지요. ^^<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1602
(왼쪽의 숫자를 입력해야 합니다.)