这是一个复杂的情况,请耐心等待。
我有一个BagOfProperties
类,我想为它创建一个实例AsyncLocal
,像这样
private readonly static System.Threading.AsyncLocal<BagOfProperties> _bag = new();
private static BagOfProperties Bag => _bag.Value ??= new BagOfProperties();
在代码中的任何地方,我现在通过Bag
变量访问属性,很好地使它们都成为AsyncLocal。
现在,由于原因,我希望能够重置这些属性。容易实现:
private static void ResetBag()
{
_bag.Value = null;
}
由于_bag
是AsyncLocal
,这在任何时候都是安全的,使用包的现有任务仍然可以访问旧值,但新任务将首先调用构造函数。
下一步是,我想从WinForms的Application.Idle
事件处理程序调用ResetBag
。这是它中断的地方(在某些竞速条件下-取决于我放置断点的位置,它可能工作)。
发生的是,_bag.Value
被设置为null
,但然后我们继续堆栈直到System.Threading.ExecutionContext.RunInternal
,System.Threading.ExecutionContextSwitcher.Undo()
被调用,_bag.Value
被恢复到以前的版本。
在整个堆栈上甚至没有一个async调用!
我添加了以下ValueChangedHandler。
private static void ValueChangedHandler(System.Threading.AsyncLocalValueChangedArgs<BagOfProperties> asyncLocalValueChangedArgs)
{
if (System.Threading.Thread.CurrentThread.ManagedThreadId == 1)
{
var previousValue = asyncLocalValueChangedArgs.PreviousValue;
var currentValue = asyncLocalValueChangedArgs.CurrentValue;
var contextChanged = asyncLocalValueChangedArgs.ThreadContextChanged;
}
}
我只对ManagedThreadId == 1
感兴趣,因为现在我的代码中没有异步,但事情发生在其他线程的第三方组件中。
当值被设置为null时,堆栈看起来像这样(星号是我的)。
MyHelper.dll!MyApp.Class1.ValueChangedHandler(System.Threading.AsyncLocalValueChangedArgs<MyApp.Class1.BagOfProperties> asyncLocalValueChangedArgs = {System.Threading.AsyncLocalValueChangedArgs<MyApp.Class1.BagOfProperties>}) Line 28 C#
mscorlib.dll!System.Threading.AsyncLocal<MyApp.Class1.BagOfProperties>.System.Threading.IAsyncLocal.OnValueChanged(object previousValueObj, object currentValueObj, bool contextChanged) Unknown
mscorlib.dll!System.Threading.ExecutionContext.SetLocalValue(System.Threading.IAsyncLocal local, object newValue, bool needChangeNotifications) Unknown
mscorlib.dll!System.Threading.AsyncLocal<System.__Canon>.Value.set(System.__Canon value) Unknown
MyHelper.dll!MyApp.Class1.ResetBag() Line 21 C#
[Native to Managed Transition]
[Managed to Native Transition]
* MyFramework.Win.dll!MyFramework.MyUtils.Application_Idle(object sender = {System.Threading.Thread}, System.EventArgs e = {System.EventArgs}) Line 3872 C#
System.Windows.Forms.dll!System.Windows.Forms.Application.RaiseIdle(System.EventArgs e) Unknown
MyFramework.Win.dll!MyFramework.MyUtils.TrackPopupMenu.AnonymousMethod__136_1() Line 3697 C#
[Native to Managed Transition]
[Managed to Native Transition]
mscorlib.dll!System.Delegate.DynamicInvokeImpl(object[] args) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackDo(System.Windows.Forms.Control.ThreadMethodEntry tme) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(object obj) Unknown
* mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallback(System.Windows.Forms.Control.ThreadMethodEntry tme) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbacks() Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) Unknown
System.Windows.Forms.dll!System.Windows.Forms.ScrollableControl.WndProc(ref System.Windows.Forms.Message m) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Form.WndProc(ref System.Windows.Forms.Message m) Unknown
MyFramework.Win.dll!MyFramework.MyForm.WndProc(ref System.Windows.Forms.Message m = {System.Windows.Forms.Message}) Line 4309 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) Unknown
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg = 49641, System.IntPtr wparam, System.IntPtr lparam) Unknown
[Native to Managed Transition]
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason = -1, int pvLoopData = 0) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason = -1, System.Windows.Forms.ApplicationContext context = {System.Windows.Forms.ApplicationContext}) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.Run(System.Windows.Forms.Form mainForm) Unknown
MyApp.exe!MyApp.Program.Main() Line 25 C#
值恢复后的堆栈是这样的:
MyHelper.dll!MyApp.Class1.ValueChangedHandler(System.Threading.AsyncLocalValueChangedArgs<MyApp.Class1.BagOfProperties> asyncLocalValueChangedArgs = {System.Threading.AsyncLocalValueChangedArgs<MyApp.Class1.BagOfProperties>}) Line 28 C#
mscorlib.dll!System.Threading.AsyncLocal<MyApp.Class1.BagOfProperties>.System.Threading.IAsyncLocal.OnValueChanged(object previousValueObj, object currentValueObj, bool contextChanged) Unknown
mscorlib.dll!System.Threading.ExecutionContext.OnAsyncLocalContextChanged(System.Threading.ExecutionContext previous = {System.Threading.ExecutionContext}, System.Threading.ExecutionContext current = {System.Threading.ExecutionContext}) Unknown
mscorlib.dll!System.Threading.ExecutionContextSwitcher.Undo() Unknown
* mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallback(System.Windows.Forms.Control.ThreadMethodEntry tme) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbacks() Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) Unknown
System.Windows.Forms.dll!System.Windows.Forms.ScrollableControl.WndProc(ref System.Windows.Forms.Message m) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Form.WndProc(ref System.Windows.Forms.Message m) Unknown
MyFramework.Win.dll!MyFramework.MyForm.WndProc(ref System.Windows.Forms.Message m = {System.Windows.Forms.Message}) Line 4309 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) Unknown
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg = 49641, System.IntPtr wparam, System.IntPtr lparam) Unknown
[Native to Managed Transition]
[Managed to Native Transition]
System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr dwComponentID, int reason = -1, int pvLoopData = 0) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason = -1, System.Windows.Forms.ApplicationContext context = {System.Windows.Forms.ApplicationContext}) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Application.Run(System.Windows.Forms.Form mainForm) Unknown
MyApp.exe!MyApp.Program.Main() Line 25 C#
是否有办法确保ResetBag
将与正确的ExecutionContext
一起运行?顺便说一句,AsyncLocal.Value
的实现在这里。
应用程序是。net Framework 4.8, Windows Forms.
这似乎是你试图使用AsyncLocal<T>
的方式,它不是设计的。异步本地值向下流动;调用堆栈永远不会"up"。理想情况下,它们是不可变的,这迫使代码设置Value
,而不是依赖于副作用(这在async local中很难考虑)。
听起来你想要的更像是快照语义;我会查看System.Collections.Immutable(听起来像ImmutableDictionary
可以很好地为您服务)。让每个任务锁定并复制当前值(必要时惰性创建),然后使用其本地副本;让您的空闲处理程序锁定并复制当前值并将其设置为null
。我认为没有必要用AsyncLocal<T>
来描述当前的问题。
也许您可以采用与dotnet团队使用HttpContextAccessor相同的方法,因此您可以在BagOfProperties
周围创建包装器并将其引用保存在AsyncLocal
而不是实际的BagOfProperties
上。
private class BagOfPropertiesWrapper
{
public BagOfProperties BagOfProperties { get; set; } = new();
}
private static AsyncLocal<BagOfPropertiesWrapper> _bagWrapper = new();
private static BagOfProperties Bag()
{
_bagWrapper.Value ??= new BagOfPropertiesWrapper();
return _bagWrapper.Value.BagOfProperties;
}
private static void ResetBag()
{
if (_bagWrapper.Value is not null)
{
_bagWrapper.Value.BagOfProperties = null;
}
}
所以现在当你做ResetBag
时,你将清除BagOfProperties
,这是从BagOfPropertiesWrapper
引用的,这是由AsyncLocal流动的。
我已经找到了ExecutionContext
中不希望发生变化的原因,以及针对我的情况的解决方法。
原因在堆栈的这一部分。
MyHelper.dll!MyApp.Class1.ResetBag() Line 21 C#
[Native to Managed Transition]
[Managed to Native Transition]
MyFramework.Win.dll!MyFramework.MyUtils.Application_Idle(object sender = {System.Threading.Thread}, System.EventArgs e = {System.EventArgs}) Line 3872 C#
System.Windows.Forms.dll!System.Windows.Forms.Application.RaiseIdle(System.EventArgs e) Unknown
MyFramework.Win.dll!MyFramework.MyUtils.TrackPopupMenu.AnonymousMethod__136_1() Line 3697 C#
[Native to Managed Transition]
[Managed to Native Transition]
mscorlib.dll!System.Delegate.DynamicInvokeImpl(object[] args) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackDo(System.Windows.Forms.Control.ThreadMethodEntry tme) Unknown
看起来是这样的,因为,出于不相关的原因,我有以下代码:
aForm.BeginInvoke(new Action(() => Application.RaiseIdle(EventArgs.Empty)));
事实证明,Control.BeginInvoke
具有更改ExecutionContext
的副作用(在我的情况下是不希望的),使回调使用"main"的副本ExecutionContext
而不是主要的东西。
我已将其更改为以下内容:
var flowControl = System.Threading.ExecutionContext.SuppressFlow();
f.BeginInvoke(new Action(() =>
{
flowControl.Undo();
Application.RaiseIdle(EventArgs.Empty);
}));
这解决了我的问题,但是如果在调用SuppressFlow
和Undo
之间创建Task
可能会引入一个问题:该任务将意外地操作ExecutionContext
/AsyncLocal
s的主版本(即与我当前的问题相反)。