为什么我有时会在 BeginInvoke 块中得到 NullReferenceException?
Why do I sometimes get a NullReferenceException in the BeginInvoke block?
所以在我的 XXX.OnPropertyChanged() 方法中我有:
public class XXX : IProperyNotifyChanged {
Control itsCtrl;
...
public void Init(Control ctrl) {
itsCtrl = ctrl;
}
public void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
if (itsCtrl.InvokeRequired) {
itsCtrl.BeginInvoke(() => {
PropertyChanged(this, propertyName);
});
} else {
PropertyChanged(this, propertyName);
}
}
}
}
我认为这会抛出以下异常(很少发生,但现在更常发生):
System.Reflection.TargetInvocationException was unhandled
HResult=-2146232828
Message=Exception has been thrown by the target of an invocation.
Source=mscorlib
StackTrace:
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallbacks()
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Form.WndProc(Message& m)
at DevExpress.XtraEditors.XtraForm.WndProc(Message& msg)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at DevExpress.Utils.Win.Hook.ControlWndHook.CallWindowProc(IntPtr pPrevProc, IntPtr hWnd, Int32 message, IntPtr wParam, IntPtr lParam)
at DevExpress.Utils.Win.Hook.ControlWndHook.WindowProc(IntPtr hWnd, Int32 message, IntPtr wParam, IntPtr lParam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at Client.Program.Main() in C:\Client\Program.cs:line 18
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=XXX
StackTrace:
at XXX.<>c__DisplayClass442_0.<OnPropertyChanged>b__0()
InnerException:
我只是在想。发生这种情况是因为我在调用 BeginInvoke
之前没有像 this
和 propertyName
那样正确地复制变量吗?或者是别的什么?这种情况很少发生,以至于我不确定如何重现它,而且我真的无法从堆栈跟踪中获得太多信息。你会如何解决这个问题?
我强烈建议使用 C# 6.0 带来的空条件运算符,如果可以的话:
itsCtrl.InvokeRequired(...) should be itsCtrl?.InvokeRequired(...)
itsCtrl.BeginInvoke(...) should be itsCtrl?.BeginInvoke(...)
与您所相信的不同,在加载表单时,您的控件可能 null
,因此您从竞争条件中获得异常。
您应该对 PropertyChanged 调用执行相同的操作:
PropertyChanged(...) should be PropertyChanged?.Invoke(...)
这是线程安全的,可以避免您的检查 if (PropertyChanged != null)
由于其他线程更改而不再正确的情况。
I was just thinking. Is this happening because I am not copying variables properly like this and propertyName before calling BeginInvoke?
this
本质上总是在栈上,不能赋值给别的东西,所以不能在方法内设置为null。 propertyName
是本地的,所以那里不能比赛。
PropertyChanged
虽然不是本地的,但是每次都获取。当你这样做时:
if (PropertyChanged != null)
{
PropertyChanged.BeginInvoke(…);
}
它的作用如下:
PropertyChangedEventHandler local1 = PropertyChanged; // Get value from property;
if (local1 != null)
{
PropertyChangedEventHandler local2 = PropertyChanged; // Get value from property;
local2.BeginInvoke(…);
}
同时 PropertyChanged
有机会被设置为 null。 那是您想复制的内容:
var propChanged = PropertyChanged;
if (propChanged != null)
{
propChanged.BeginInvoke(…);
}
现在 propChanged
在整个方法的持续时间内要么为空,要么不为空,比赛结束。
的确如此:
PropertyChanged?.BeginInvoke(…);
所以在我的 XXX.OnPropertyChanged() 方法中我有:
public class XXX : IProperyNotifyChanged {
Control itsCtrl;
...
public void Init(Control ctrl) {
itsCtrl = ctrl;
}
public void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
if (itsCtrl.InvokeRequired) {
itsCtrl.BeginInvoke(() => {
PropertyChanged(this, propertyName);
});
} else {
PropertyChanged(this, propertyName);
}
}
}
}
我认为这会抛出以下异常(很少发生,但现在更常发生):
System.Reflection.TargetInvocationException was unhandled
HResult=-2146232828
Message=Exception has been thrown by the target of an invocation.
Source=mscorlib
StackTrace:
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallbacks()
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Form.WndProc(Message& m)
at DevExpress.XtraEditors.XtraForm.WndProc(Message& msg)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at DevExpress.Utils.Win.Hook.ControlWndHook.CallWindowProc(IntPtr pPrevProc, IntPtr hWnd, Int32 message, IntPtr wParam, IntPtr lParam)
at DevExpress.Utils.Win.Hook.ControlWndHook.WindowProc(IntPtr hWnd, Int32 message, IntPtr wParam, IntPtr lParam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at Client.Program.Main() in C:\Client\Program.cs:line 18
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=XXX
StackTrace:
at XXX.<>c__DisplayClass442_0.<OnPropertyChanged>b__0()
InnerException:
我只是在想。发生这种情况是因为我在调用 BeginInvoke
之前没有像 this
和 propertyName
那样正确地复制变量吗?或者是别的什么?这种情况很少发生,以至于我不确定如何重现它,而且我真的无法从堆栈跟踪中获得太多信息。你会如何解决这个问题?
我强烈建议使用 C# 6.0 带来的空条件运算符,如果可以的话:
itsCtrl.InvokeRequired(...) should be itsCtrl?.InvokeRequired(...)
itsCtrl.BeginInvoke(...) should be itsCtrl?.BeginInvoke(...)
与您所相信的不同,在加载表单时,您的控件可能 null
,因此您从竞争条件中获得异常。
您应该对 PropertyChanged 调用执行相同的操作:
PropertyChanged(...) should be PropertyChanged?.Invoke(...)
这是线程安全的,可以避免您的检查 if (PropertyChanged != null)
由于其他线程更改而不再正确的情况。
I was just thinking. Is this happening because I am not copying variables properly like this and propertyName before calling BeginInvoke?
this
本质上总是在栈上,不能赋值给别的东西,所以不能在方法内设置为null。 propertyName
是本地的,所以那里不能比赛。
PropertyChanged
虽然不是本地的,但是每次都获取。当你这样做时:
if (PropertyChanged != null)
{
PropertyChanged.BeginInvoke(…);
}
它的作用如下:
PropertyChangedEventHandler local1 = PropertyChanged; // Get value from property;
if (local1 != null)
{
PropertyChangedEventHandler local2 = PropertyChanged; // Get value from property;
local2.BeginInvoke(…);
}
同时 PropertyChanged
有机会被设置为 null。 那是您想复制的内容:
var propChanged = PropertyChanged;
if (propChanged != null)
{
propChanged.BeginInvoke(…);
}
现在 propChanged
在整个方法的持续时间内要么为空,要么不为空,比赛结束。
的确如此:
PropertyChanged?.BeginInvoke(…);