簡體   English   中英

TPL和假冒

[英]TPL and Impersonation

我正在使用Impersonator類(請參閱http://www.codeproject.com/KB/cs/zetaimpersonator.aspx )在運行時切換用戶上下文。

與此同時,我現在正在將我的程序從單線程設計重構為多線程設計(使用TPL /主要是Task類型,即)。

由於這種模擬是在線程級別上使用本機API函數發生的事情,我想知道TPL與它兼容的程度。 如果我更改了任務中的用戶上下文,那么如果任務完成並且線程返回到ThreadPool,那么該用戶上下文是否仍然設置? 在此任務中啟動的其他任務是否會隱式使用該上下文?

我試圖自己找出單元測試,並從第一次單元測試中扣除:

  • 任務在線程內部啟動,而模擬“神奇地”繼承用戶上下文。
  • 當原始任務/線程執行其Impersonation.Undo()時,不會撤消繼承的模擬。

第二個單元測試顯示,如果未明確撤消模擬,則用戶上下文在返回到線程池的線程上“幸存”,並且其他后續任務現在可以在不同的用戶上下文中隨機運行,具體取決於它們被分配的線程至。

我的問題:有沒有比通過本機API調用實現模擬更好的方法? 也許一個更專注於TPL並綁定到任務而不是線程? 如果有一個變化來減輕在隨機環境中執行任務的風險,我很樂意這樣做......

這些是我寫的2個單元測試。 如果您想自己運行測試,則必須稍微修改代碼以使用您自己的機制來接收用戶憑據,並且肯定可以輕松刪除log4net調用。

是的,我知道,Thread.Sleep()是不好的風格,我因為在那里懶惰而感到內疚... ;-)

  private string RetrieveIdentityUser()
  {
     var windowsIdentity = WindowsIdentity.GetCurrent();
     if (windowsIdentity != null)
     {
        return windowsIdentity.Name;
     }
     return null;
  }

  [TestMethod]
  [TestCategory("LocalTest")]
  public void ThreadIdentityInheritanceTest()
  {
     string user;
     string pw;
     Security.Decode(CredentialsIdentifier, out user, out pw);

     string userInMainThread = RetrieveIdentityUser();
     string userInTask1BeforeImpersonation = null;
     string userInTask1AfterImpersonation = null;
     string userInTask2 = null;
     string userInTask3 = null;
     string userInTask2AfterImpersonationUndo = null;

     var threadlock = new object();
     lock (threadlock)
     {
        new Task(
           () =>
           {
              userInTask1BeforeImpersonation = RetrieveIdentityUser();
              using (new Impersonator(user, Domain, pw))
              {
                 userInTask1AfterImpersonation = RetrieveIdentityUser();
                 lock (threadlock)
                 {
                    Monitor.Pulse(threadlock);
                 }
                 new Task(() =>
                 {
                    userInTask2 = RetrieveIdentityUser();
                    Thread.Sleep(200);
                    userInTask2AfterImpersonationUndo = RetrieveIdentityUser();
                 }).Start();
                 Thread.Sleep(100);
              }
           }).Start();

        Monitor.Wait(threadlock);
        RetrieveIdentityUser();
        new Task(() => { userInTask3 = RetrieveIdentityUser(); }).Start();
        Thread.Sleep(300);

        Assert.IsNotNull(userInMainThread);
        Assert.IsNotNull(userInTask1BeforeImpersonation);
        Assert.IsNotNull(userInTask1AfterImpersonation);
        Assert.IsNotNull(userInTask2);
        Assert.IsNotNull(userInTask3);

        // context in both threads equal before impersonation
        Assert.AreEqual(userInMainThread, userInTask1BeforeImpersonation);

        // context has changed in task1
        Assert.AreNotEqual(userInTask1BeforeImpersonation, userInTask1AfterImpersonation);

        // impersonation to the expected user
        Assert.AreEqual(Domain + "\\" + user, userInTask1AfterImpersonation);

        // impersonation is inherited
        Assert.AreEqual(userInTask1AfterImpersonation, userInTask2);

        // a newly started task from the main thread still shows original user context
        Assert.AreEqual(userInMainThread, userInTask3);

        // inherited impersonation is not revoked
        Assert.AreEqual(userInTask2, userInTask2AfterImpersonationUndo);
     }
  }

  [TestMethod]
  [TestCategory("LocalTest")]
  public void TaskImpersonationTest()
  {
     int tasksToRun = 100; // must be more than the minimum thread count in ThreadPool
     string userInMainThread = RetrieveIdentityUser();
     var countdownEvent = new CountdownEvent(tasksToRun);
     var exceptions = new List<Exception>();
     object threadLock = new object();
     string user;
     string pw;
     Security.Decode(CredentialsIdentifier, out user, out pw);
     for (int i = 0; i < tasksToRun; i++)
     {
        new Task(() =>
        {
           try
           {
              try
              {
                 Logger.DebugFormat("Executing task {0} on thread {1}...", Task.CurrentId, Thread.CurrentThread.GetHashCode());
                 Assert.AreEqual(userInMainThread, RetrieveIdentityUser());
                 //explicitly not disposing impersonator / reverting impersonation
                 //to see if a thread reused by TPL has its user context reset
                 // ReSharper disable once UnusedVariable
                 var impersonator = new Impersonator(user, Domain, pw);
                 Assert.AreEqual(Domain + "\\" + user, RetrieveIdentityUser());
              }
              catch (Exception e)
              {
                 lock (threadLock)
                 {
                    var newException = new Exception(string.Format("Task {0} on Thread {1}: {2}", Task.CurrentId, Thread.CurrentThread.GetHashCode(), e.Message));
                    exceptions.Add(newException);
                    Logger.Error(newException);
                 }
              }
           }
           finally
           {
              countdownEvent.Signal();
           }
        }).Start();
     }
     if (!countdownEvent.Wait(TimeSpan.FromSeconds(5)))
     {
        throw new TimeoutException();
     }
     Assert.IsTrue(exceptions.Any());
     Assert.AreEqual(typeof(AssertFailedException), exceptions.First().InnerException.GetType());
  }

}

WindowsIdentity (通過模擬更改)存儲在SecurityContext 您可以通過各種方式確定此模擬如何“流動”。 既然你提到過,你正在使用p / invoke,請注意SecurityContext文檔中的警告:

公共語言運行庫(CLR)知道僅使用托管代碼執行的模擬操作,而不是在托管代碼之外執行的模擬,例如通過平台調用...

但是,我並不完全確定模仿是真的從一個任務繼承到另一個任務,或者您觀察的行為是否是由於任務內聯。 在某些情況下,新任務可能在同一個線程池線程上同步執行。 你可以在這里找到一個很好的討論。

盡管如此,如果您想確保某些任務始終在模擬下運行,而其他任務卻沒有,我可以建議您查看自定義任務計划程序。 MSDN上有關於如何編寫自己的文檔,包括可用作起點的幾種常見調度程序的代碼示例

由於模擬是每線程設置,因此您可以擁有自己的任務調度程序,該任務調度程序在執行任務時保留在模擬下運行的一個線程(或幾個線程)。 當你有許多小工作單元時,這也可以減少你進出模擬的次數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM