为什么我有时会在BeginInvoke块中获得NullReferenceException?

时间:2016-03-10 23:36:00

标签: c# .net invoke 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: 

我只是在想。是否发生这种情况是因为我在调用this之前没有像propertyNameBeginInvoke那样正确复制变量?或者是别的什么?这种情况很少发生,我不确定如何重现它,我无法从堆栈跟踪中获得更多。你会如何解决这个问题?

2 个答案:

答案 0 :(得分:2)

  

我只是在想。这是否发生是因为我在调用BeginInvoke之前没有像this和propertyName那样正确地复制变量?

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(…);

答案 1 :(得分:1)

我强烈建议您使用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)不再成立的情况。