【发布时间】:2014-07-22 13:33:01
【问题描述】:
我正在使用 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());
}
}
【问题讨论】:
标签: c# task-parallel-library impersonation