设置 Thread.CurrentPrincipal 为 async/await
Setting Thread.CurrentPrincipal with async/await
下面是我尝试在异步方法中将 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
}
I know there are thread changes but thought async/await would handle synching this for me.
async
/await
本身不对线程本地数据进行任何同步。不过,如果您想进行自己的同步,它确实具有 "hook" 的功能。
默认情况下,当您 await
一个任务时,它将捕获当前的 "context"(即 SynchronizationContext.Current
,除非它是 null
,在这种情况下它是 TaskScheduler.Current
)。当 async
方法恢复时,它将在该上下文中恢复。
因此,如果您想定义 "context",您可以通过定义自己的 SynchronizationContext
来实现。不过,这并不容易。特别是如果您的应用需要在 ASP.NET 上 运行,这需要它自己的 AspNetSynchronizationContext
(并且它们不能嵌套或任何东西 - 您只能得到一个)。 ASP.NET 使用其 SynchronizationContext
设置 Thread.CurrentPrincipal
。
但是,请注意 远离 SynchronizationContext
。 ASP.NET vNext 没有。 OWIN 从来没有(AFAIK)。自托管 SignalR 也没有。通常认为以 some 的方式传递值更合适——无论这对方法是显式的,还是注入到包含此方法的类型的成员变量中。
如果您真的不想传递该值,那么您还可以采用另一种方法:async
-等价于ThreadLocal
.核心思想是将不可变值存储在 LogicalCallContext
中,异步方法适当地继承了它。我在我的博客上介绍了这个 "AsyncLocal"(有传言说 AsyncLocal
可能会出现在 .NET 4.6 中,但在那之前你必须自己动手)。请注意,您无法使用 AsyncLocal
技术读取 Thread.CurrentPrincipal
;您必须更改所有代码才能使用 MyAsyncValues.CurrentPrincipal
.
Thread.CurrentPrincipal 存储在线程本地存储中的 ExecutionContext 中。
在另一个线程上执行委托时(使用 Task.Run 或 ThreadPool.QueueWorkItem),ExecutionContext 从当前线程中捕获,委托被包裹在 ExecutionContext.Run 中。因此,如果您在调用 Task.Run 之前设置 CurrentPrincipal,它仍会在 Delegate 中设置。
现在您的问题是您更改了 Task.Run 中的 CurrentPrincipal 并且 ExecutionContext 仅以一种方式流动。我认为这是大多数情况下的预期行为,解决方案是在开始时设置 CurrentPrincipal。
在任务中更改 ExecutionContext 时,您最初想要的是不可能的,因为 Task.ContinueWith 也捕获了 ExecutionContext。要做到这一点,您必须在委托 运行 之后立即以某种方式捕获 ExecutionContext,然后在自定义等待者的延续中将其流回,但这将是非常邪恶的。
ExecutionContext
,其中包含 SecurityContext
,其中包含 CurrentPrincipal
,几乎总是流经所有异步分叉。因此,在您的 Task.Run()
委托中,您 - 在您注意到的单独线程上,获得相同的 CurrentPrincipal
。但是,在幕后,您可以通过 ExecutionContext.Run(...) 获取上下文,其中指出:
The execution context is returned to its previous state when the
method completes.
我发现自己处于与 Stephen Cleary 不同的奇怪领域 :),但我不明白 SynchronizationContext
与此有何关系。
Stephen Toub 在一篇出色的文章中涵盖了大部分内容 here。
您可以使用自定义等待程序来流动 CurrentPrincipal
(或任何线程属性,就此而言)。下面的示例显示了它是如何完成的,灵感来自 Stephen Toub's 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);
代码(只进行了非常轻微的测试):
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);
}
}
}
下面是我尝试在异步方法中将 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
}
I know there are thread changes but thought async/await would handle synching this for me.
async
/await
本身不对线程本地数据进行任何同步。不过,如果您想进行自己的同步,它确实具有 "hook" 的功能。
默认情况下,当您 await
一个任务时,它将捕获当前的 "context"(即 SynchronizationContext.Current
,除非它是 null
,在这种情况下它是 TaskScheduler.Current
)。当 async
方法恢复时,它将在该上下文中恢复。
因此,如果您想定义 "context",您可以通过定义自己的 SynchronizationContext
来实现。不过,这并不容易。特别是如果您的应用需要在 ASP.NET 上 运行,这需要它自己的 AspNetSynchronizationContext
(并且它们不能嵌套或任何东西 - 您只能得到一个)。 ASP.NET 使用其 SynchronizationContext
设置 Thread.CurrentPrincipal
。
但是,请注意 远离 SynchronizationContext
。 ASP.NET vNext 没有。 OWIN 从来没有(AFAIK)。自托管 SignalR 也没有。通常认为以 some 的方式传递值更合适——无论这对方法是显式的,还是注入到包含此方法的类型的成员变量中。
如果您真的不想传递该值,那么您还可以采用另一种方法:async
-等价于ThreadLocal
.核心思想是将不可变值存储在 LogicalCallContext
中,异步方法适当地继承了它。我在我的博客上介绍了这个 "AsyncLocal"(有传言说 AsyncLocal
可能会出现在 .NET 4.6 中,但在那之前你必须自己动手)。请注意,您无法使用 AsyncLocal
技术读取 Thread.CurrentPrincipal
;您必须更改所有代码才能使用 MyAsyncValues.CurrentPrincipal
.
Thread.CurrentPrincipal 存储在线程本地存储中的 ExecutionContext 中。
在另一个线程上执行委托时(使用 Task.Run 或 ThreadPool.QueueWorkItem),ExecutionContext 从当前线程中捕获,委托被包裹在 ExecutionContext.Run 中。因此,如果您在调用 Task.Run 之前设置 CurrentPrincipal,它仍会在 Delegate 中设置。
现在您的问题是您更改了 Task.Run 中的 CurrentPrincipal 并且 ExecutionContext 仅以一种方式流动。我认为这是大多数情况下的预期行为,解决方案是在开始时设置 CurrentPrincipal。
在任务中更改 ExecutionContext 时,您最初想要的是不可能的,因为 Task.ContinueWith 也捕获了 ExecutionContext。要做到这一点,您必须在委托 运行 之后立即以某种方式捕获 ExecutionContext,然后在自定义等待者的延续中将其流回,但这将是非常邪恶的。
ExecutionContext
,其中包含 SecurityContext
,其中包含 CurrentPrincipal
,几乎总是流经所有异步分叉。因此,在您的 Task.Run()
委托中,您 - 在您注意到的单独线程上,获得相同的 CurrentPrincipal
。但是,在幕后,您可以通过 ExecutionContext.Run(...) 获取上下文,其中指出:
The execution context is returned to its previous state when the method completes.
我发现自己处于与 Stephen Cleary 不同的奇怪领域 :),但我不明白 SynchronizationContext
与此有何关系。
Stephen Toub 在一篇出色的文章中涵盖了大部分内容 here。
您可以使用自定义等待程序来流动 CurrentPrincipal
(或任何线程属性,就此而言)。下面的示例显示了它是如何完成的,灵感来自 Stephen Toub's 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);
代码(只进行了非常轻微的测试):
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);
}
}
}