Reputation: 7053
Error: Object reference not set to an instance of an object.
The algorithm below works.
I tried it, then I removed the Winform
project to another directory and SynchronizationContext.Current
is null
.
Why?
SynchronizationContext uiCtx = SynchronizationContext.Current;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int[] makeSelfMoves = new int[4];
lock (replay)
{
// count should be more than 2
foreach (KeyValuePair<int, int[]> item in replay)
{
makeSelfMoves = replay[item.Key];
codeFile.ExecuteAll(makeSelfMoves[0],
makeSelfMoves[1], makeSelfMoves[2], makeSelfMoves[3]);
// i get the error here. uictx is null
uiCtx.Post(o =>
{
PrintPieces(codeFile.PieceState());
}, null);
System.Threading.Thread.Sleep(1000);
}
}
}
Upvotes: 14
Views: 20182
Reputation: 101
I ran into this issue when creating WinForms via dependency injection in my test framework. Originally I would capture the SynchronizationContext.Current in my constructor:
private readonly SynchronizationContext UISyncCtxt;
public MyWinFormViewModel ()
{
UISyncCtxt = SynchronizationContext.Current;
...
}
This worked fine if this MyWinFormViewModel was created while an application was already running, but this isn't necessarily the situation when creating the dependency graph in the test harness. When created by the test harness, SynchronizationContext.Current would be null, and there'd be a null reference exception later.
My solution was to "lazily" evaluate it as follows:
private SynchronizationContext _uisyncctxt;
private SynchronizationContext UISyncCtxt =>
_uisyncctxt ??= SynchronizationContext.Current;
By the time I actually need the context (to update controls on the form), it's assured to be present (because the form has been instantiated).
EDIT: Peter Duniho brought up a valid point about arbitrarily grabbing the synchronization context. My original answer also makes this class dishonest about its dependencies since it relies on this context, but doesn't request it through the constructor or other injectable method. Since this class uses DI, I added a dependency called IUISyncContext, which has the following signature:
public interface IUISyncContext
{
SynchronizationContext UISyncContext { get; }
}
...and the constructor for my view model:
private readonly SynchronizationContext UISyncCtxt;
public MyWinFormViewModel (IUISyncContext syncContext)
{
UISyncCtxt = syncContext.UISyncContext;
...
}
Thanks for the feedback, Peter.
Upvotes: 1
Reputation: 941217
Your code critically depends on exactly when and where the constructor of your class runs. SynchronizationContext.Current will be null when:
your class object is created too soon, before your code creates an instance of the Form class or calls Application.Run() in Main(). That's when the Current member is set to an instance of WindowsFormsSynchronizationContext, the class that knows how to marshal calls with the message loop. Fix this by moving your object instancing code to the main form constructor.
your class object is created on any thread other than the main UI thread. Only the UI thread in a Winforms application can marshal calls. Diagnose this by adding a constructor to your class with this statement:
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
Also add this line to the Main() method in Program.cs. It won't work if the displayed value in the Output window is different. Fix this by moving your object instancing code to, again, the main form constructor so you can be sure it runs on the UI thread.
Upvotes: 28