2016-03-10 3 views
0

Donc, dans ma méthode XXX.OnPropertyChanged() J'ai:Pourquoi ai-je parfois une exception NullReferenceException dans le bloc BeginInvoke?

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); 
     } 
     } 
    } 
} 

Je pense que cela jette l'exception suivante (rarement, mais arrive plus souvent maintenant):

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: 

Je pensais juste. Est-ce que cela se produit parce que je ne copie pas correctement les variables comme this et propertyName avant d'appeler BeginInvoke? Ou s'agit-il d'autre chose? Cela arrive si rarement que je ne suis pas sûr de savoir comment le reproduire et je ne peux pas vraiment obtenir beaucoup de la trace de la pile. Comment voulez-vous résoudre ce problème?

+0

est "itsCtrl" jamais nulle? Ajoutez une vérification null avant de commencer à invoquer et de voir si le problème disparaît – bgura

+0

itsCtrl est le formulaire principal. Cela ne peut jamais être nul. – Denis

+0

@Denis Ce n'est pas vrai. Lorsque le formulaire est en cours de chargement, il peut être 'null'. Vous rencontrez une condition de concurrence. –

Répondre

2

Je pensais juste. Est-ce que cela se produit parce que je ne copie pas correctement les variables comme ceci et propertyName avant d'appeler BeginInvoke?

this est toujours en soi sur la pile, et ne peut être affecté à quelque chose d'autre, de sorte qu'il ne pouvait pas être réglé à zéro dans la méthode. propertyName est local, donc il ne peut y avoir de course là-bas.

PropertyChanged bien que ce ne soit pas local, mais obtenu à chaque fois. Lorsque vous faites:

if (PropertyChanged != null) 
{ 
    PropertyChanged.BeginInvoke(…); 
} 

Il agit comme:

PropertyChangedEventHandler local1 = PropertyChanged; // Get value from property; 
if (local1 != null) 
{ 
    PropertyChangedEventHandler local2 = PropertyChanged; // Get value from property; 
    local2.BeginInvoke(…); 
} 

Il y a une occasion pour PropertyChanged d'être mis à zéro dans l'intervalle. C'est ce que vous voulez faire une copie:

var propChanged = PropertyChanged; 
if (propChanged != null) 
{ 
    propChanged.BeginInvoke(…); 
} 

Maintenant, soit propChanged sera nulle pour la durée de toute la méthode, ou il ne sera pas, et la course a disparu.

De fait:

PropertyChanged?.BeginInvoke(…); 
+0

Si je fais PropertyChanged.BeginInvoke(). PropChanged est un événement. Quel fil pourrait-il exécuter? Supposons que je l'appelle à partir d'un thread non-GUI ... – Denis

+0

Si vous avez un risque de cela, puis refaire la vérification nulle là (l'utilisation de la syntaxe de vérification null l'autre réponse mentionnée que j'étais sceptique de serait en effet bien là) –

1

Je vous suggère fortement en utilisant l'opérateur null conditionnelle provoquée par C# 6.0, si vous le pouvez:

itsCtrl.InvokeRequired(...) should be  itsCtrl?.InvokeRequired(...) 
itsCtrl.BeginInvoke(...)  should be  itsCtrl?.BeginInvoke(...) 

Contrairement à ce que vous croyez, alors que la forme est en cours de téléchargement, votre contrôle peut être null, et donc vous obtenez l'exception d'une condition de course.

Vous devriez faire la même chose à votre appel PropertyChanged:

PropertyChanged(...) should be PropertyChanged?.Invoke(...) 

C'est thread-safe et éviter la situation dans laquelle votre chèque if (PropertyChanged != null) est plus vrai à cause d'un autre thread changer.

+0

'PropertyChanged ?.Invoquer (...) 'serait bien si la seule chose qui dépendait de ce que ce soit null était de savoir si cet appel' Invoke' a été fait, mais ici ce n'est pas le cas, –

+0

@JonHanna Je ne vous suis pas, pourriez-vous élaborer? Null-conditionnel est thread-safe et est exactement la même chose que vous avez écrit dans votre réponse en assignant 'PropertyChanged' à une variable locale. –

+0

Mais le local ne dure que pour cet appel. Bien qu'équivalent à la forme abrégée dans ma réponse, ce n'est pas équivalent à la question, car il y a plus de choses dans le bloc 'if' qu'un simple appel à' BeginInvoke' –