winforms AsyncLocal意外恢复其值-不涉及async/await

a2mppw5e  于 2023-04-07  发布在  其他
关注(0)|答案(1)|浏览(91)

这是一个复杂的情况,请容忍我。
我有一个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被恢复到以前的版本。
整个堆栈上甚至没有一个异步调用!
我添加了以下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窗体。

gmxoilav

gmxoilav1#

看起来你试图以一种不被设计的方式使用AsyncLocal<T>。异步本地值在调用堆栈中“向下”流动,而不是“向上”流动。理想情况下,它们是不可变的,这迫使代码设置Value,而不是依赖于副作用(这在async local中很难考虑)。
听起来你想要的更像是快照语义;我会研究System.Collections.Immutable(听起来像ImmutableDictionary会很好地为你服务),让每个任务锁定并复制当前值(如果需要的话,延迟创建),然后使用它的本地副本;让你的空闲处理程序锁定并复制当前值,并将其设置为null

相关问题