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: Not an issue. Scenario is to complex to handle in a generic way.
```
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: Not an issue. Scenario is to complex to handle in a generic way.