Sea*_*ron 9 c# asynchronous claims-based-identity async-await current-principal
下面是我试图在异步方法中将Thread.CurrentPrincipal设置为自定义UserPrincipal对象的简化版本,但是自定义对象在离开await后会丢失,即使它仍然在新的threadID 10上.
有没有办法在await中更改Thread.CurrentPrincipal并在以后使用它而不传入或返回它?或者这不安全,永远不应该异步?我知道有线程更改,但认为async/await将为我处理同步.
[TestMethod]
public async Task AsyncTest()
{
var principalType = Thread.CurrentPrincipal.GetType().Name;
// principalType = WindowsPrincipal
// Thread.CurrentThread.ManagedThreadId = 11
await Task.Run(() =>
{
// Tried putting await Task.Yield() here but didn't help
Thread.CurrentPrincipal = new UserPrincipal(Thread.CurrentPrincipal.Identity);
principalType = Thread.CurrentPrincipal.GetType().Name;
// principalType = UserPrincipal
// Thread.CurrentThread.ManagedThreadId = 10
});
principalType = Thread.CurrentPrincipal.GetType().Name;
// principalType = WindowsPrincipal (WHY??)
// Thread.CurrentThread.ManagedThreadId = 10
}
Run Code Online (Sandbox Code Playgroud)
Ste*_*ary 10
我知道有线程更改,但认为async/await将为我处理同步.
async/ await不会自行同步线程本地数据.但是,如果你想进行自己的同步,它确实有各种各样的"钩子".
默认情况下,当你await执行任务时,它将捕获当前的"上下文"(SynchronizationContext.Current除非它是null,否则就是这样TaskScheduler.Current).当async方法恢复时,它将在该上下文中恢复.
因此,如果要定义"上下文",可以通过定义自己的上下文来实现SynchronizationContext.但这并不容易.特别是如果您的应用程序需要在ASP.NET上运行,这需要自己的AspNetSynchronizationContext(并且它们不能嵌套或任何东西 - 您只需要一个).ASP.NET使用它SynchronizationContext来设置Thread.CurrentPrincipal.
但是,请注意,有一个明确的运动远从SynchronizationContext.ASP.NET vNext没有.OWIN从未做过(AFAIK).自托管SignalR也没有.通常认为以某种方式传递值更合适- 无论这是对方法是显式的,还是注入到包含此方法的类型的成员变量中.
如果你真的不想传递价值,那么你可以采取另一种方法:async等价的ThreadLocal.核心思想是将不可变值存储在a中LogicalCallContext,该值由异步方法适当地继承.我在我的博客上报道了这个"AsyncLocal"(有传言说AsyncLocal可能会在.NET 4.6中出现,但在此之前你必须自己动手).请注意,您无法Thread.CurrentPrincipal使用该AsyncLocal技术阅读; 你必须改变所有代码才能使用类似的东西MyAsyncValues.CurrentPrincipal.
Thread.CurrentPrincipal存储在ExecutionContext中,ExecutionContext存储在Thread Local Storage中.
在另一个线程上执行委托时(使用Task.Run或ThreadPool.QueueWorkItem),从当前线程捕获ExecutionContext,并将委托包装在ExecutionContext.Run中.因此,如果在调用Task.Run之前设置CurrentPrincipal,它仍将在Delegate中设置.
现在您的问题是您在Task.Run中更改CurrentPrincipal并且ExecutionContext仅以一种方式流动.我认为这是大多数情况下的预期行为,解决方案是在开始时设置CurrentPrincipal.
在Task中更改ExecutionContext时,您最初想要的是不可能的,因为Task.ContinueWith也捕获了ExecutionContext.要做到这一点,你必须在委托运行之后立即以某种方式捕获ExecutionContext,然后在自定义awaiter的延续中将其回流,但那将是非常邪恶的.
ExecutionContext, 包含SecurityContext, 包含CurrentPrincipal, 几乎总是在所有异步分叉中流动。所以在你的Task.Run()委托中,你 - 在你注意到的一个单独的线程上,得到相同的CurrentPrincipal. 但是,在幕后,您可以通过ExecutionContext.Run(...)获得上下文,其中指出:
当方法完成时,执行上下文将返回到其先前的状态。
我发现自己处于与 Stephen Cleary 不同的奇怪领域:),但我看不出SynchronizationContext这与此有什么关系。
Stephen Toub 在此处的一篇优秀文章中涵盖了大部分内容。
您可以使用自定义awaiter流CurrentPrincipal(或任何线程属性).以下示例展示了如何完成它,受到Stephen Toub的CultureAwaiter启发.它在TaskAwaiter内部使用,因此也将捕获同步上下文(如果有的话).
用法:
Console.WriteLine(Thread.CurrentPrincipal.GetType().Name);
await TaskExt.RunAndFlowPrincipal(() =>
{
Thread.CurrentPrincipal = new UserPrincipal(Thread.CurrentPrincipal.Identity);
Console.WriteLine(Thread.CurrentPrincipal.GetType().Name);
return 42;
});
Console.WriteLine(Thread.CurrentPrincipal.GetType().Name);
Run Code Online (Sandbox Code Playgroud)
代码(仅经过非常轻微的测试):
public static class TaskExt
{
// flowing Thread.CurrentPrincipal
public static FlowingAwaitable<TResult, IPrincipal> RunAndFlowPrincipal<TResult>(
Func<TResult> func,
CancellationToken token = default(CancellationToken))
{
return RunAndFlow(
func,
() => Thread.CurrentPrincipal,
s => Thread.CurrentPrincipal = s,
token);
}
// flowing anything
public static FlowingAwaitable<TResult, TState> RunAndFlow<TResult, TState>(
Func<TResult> func,
Func<TState> saveState,
Action<TState> restoreState,
CancellationToken token = default(CancellationToken))
{
// wrap func with func2 to capture and propagate exceptions
Func<Tuple<Func<TResult>, TState>> func2 = () =>
{
Func<TResult> getResult;
try
{
var result = func();
getResult = () => result;
}
catch (Exception ex)
{
// capture the exception
var edi = ExceptionDispatchInfo.Capture(ex);
getResult = () =>
{
// re-throw the captured exception
edi.Throw();
// should never be reaching this point,
// but without it the compiler whats us to
// return a dummy TResult value here
throw new AggregateException(edi.SourceException);
};
}
return new Tuple<Func<TResult>, TState>(getResult, saveState());
};
return new FlowingAwaitable<TResult, TState>(
Task.Run(func2, token),
restoreState);
}
public class FlowingAwaitable<TResult, TState> :
ICriticalNotifyCompletion
{
readonly TaskAwaiter<Tuple<Func<TResult>, TState>> _awaiter;
readonly Action<TState> _restoreState;
public FlowingAwaitable(
Task<Tuple<Func<TResult>, TState>> task,
Action<TState> restoreState)
{
_awaiter = task.GetAwaiter();
_restoreState = restoreState;
}
public FlowingAwaitable<TResult, TState> GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get { return _awaiter.IsCompleted; }
}
public TResult GetResult()
{
var result = _awaiter.GetResult();
_restoreState(result.Item2);
return result.Item1();
}
public void OnCompleted(Action continuation)
{
_awaiter.OnCompleted(continuation);
}
public void UnsafeOnCompleted(Action continuation)
{
_awaiter.UnsafeOnCompleted(continuation);
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6470 次 |
| 最近记录: |