Quantcast
Channel: Caliburn.Micro: Xaml Made Easy
Viewing all articles
Browse latest Browse all 1760

Commented Issue: DefaultCloseStrategy.Execute throws NullReferenceException [301]

$
0
0
DefaultCloseStrategy chokes up, when I use coroutines in CanClose method of ViewModel (using Screen as base) and throws NullReferenceException:

```
public override void CanClose(Action<bool> callback)
{
if (/* some condition */)
callback(true);
else
Coroutine.BeginExecute(this.CheckSave(callback).GetEnumerator());
}

private IEnumerable<IResult> CheckSave(Action<bool> callback)
{
var question = new Question("Do you really want to close?", "Question", Answer.Yes, Answer.No, Answer.Cancel);
yield return question.AsResult()
.WhenCancelled()
.Invoke(() => callback(false));
if (question.GivenResponse == Answer.Yes && this.saveAction != null)
yield return new SequentialResult(this.saveAction().GetEnumerator());
callback(true);
}
```
(Yes, I'm using Caliburn.Micro.Contrib (hence unknown ```WhenCancelled()``` extension method on ```IResult```), but that doesn't have any influence on the issue)

The culprit seems to be that when CheckSave() coroutine returns SequentialResult, coroutine execution "falls through" back to CanClose() method. DefaultCloseStrategy continues execution as normally (ending ```do while(!guardPending)``` loop).

The real issue is when saveAction coroutine returns and CheckSave coroutine continues exection and calls ```callback()``` from DefaultCloseStrategy:
```
guard.CanClose(canClose =>{
guardPending = false;
if (canClose) {
closable.Add(current);
}
if (guardMustCallEvaluate) {
guardMustCallEvaluate = false;
Evaluate(canClose, enumerator, callback);
} else {
finalResult = finalResult && canClose;
}
});
```

At that point __closable__ field is null, thus throwing exception. I've tried to narrow this down, by putting breakpoint on ```closable = null``` line in this part of ```Evaluate``` method:

```
if (!enumerator.MoveNext()) {
callback(finalResult, closeConductedItemsWhenConductorCannotClose ? closable : new List<T>());
closable = null;
break;
}
```

Execution does not jump there, so I'm guessing garbage collection? I'm not an expert, but those delegates should keep that instance of DefaultCloseStrategy (and by extension ```closable``` field) rooted and keep them from being GCed.


Another problem is that __guardMustCallEvaluate__ is at that point __false__, thus simply recreating closable won't help.

There's at least one other person, who seems to have stumbled upon this issue:
http://stackoverflow.com/a/15466252/93944
Comments: If I understand you right, you want to close always. Then the code needs to be changed like this: ``` C# public override void CanClose(Action<bool> callback) { if (/* some condition */) //TODO: set a breakpoint here (should be hit only once) callback(true); else Coroutine.BeginExecute(this.CheckSave().GetEnumerator(), null, (s, e) => { callback(true)); //TODO: set breakpoint here (should be hint only once) } } private IEnumerable<IResult> CheckSave() { var question = new Question("Do you really want to close?", "Question", Answer.Yes, Answer.No, Answer.Cancel); yield return question.AsResult(); if (question.GivenResponse == Answer.Yes && this.saveAction != null) yield return new SequentialResult(this.saveAction().GetEnumerator()); } ``` Set a breakpoint to the lines I commented and check I they are hit only once. Without a running sample application I can't do more to help you. Sorry.

Viewing all articles
Browse latest Browse all 1760

Trending Articles