Try-catch in an iterator
Is it possible to do a try-catch, or alternatively a try-finally over a
yield return
statement?
When I try something like this:
then the Dispose method is never called.49 Replies
There actually seems to be conflicting information on this matter. It seems in the case of an iterator finally is called on the disposing of the enumerator :hmm: Yet the documentation doesn't explain the behavior of yield in try-finally, instead claiming it's not allowed at all while it's actually only disallowed when a catch is present https://learn.microsoft.com/dotnet/csharp/language-reference/statements/yield
yield statement - provide the next element in an iterator - C# refe...
Use the yield statement in iterators to provide the next value or signal the end of an iteration
@Ruttie Try disposing the enumerator mid enumeration and see if finally runs then?
I think I managed to solve the problem.
By manually iterating over the enumerator returned by Method2 and try-catching only the movenext, I can then record the exception
though this only works for one 'level' of enumerator
Are you expecting MoveNext to throw an exception? It typically just returns false in the worst case
Actually nevermind mutable collections to throw exceptions when modified
well, it should be noted that Method2 does not return something like a list, but rather is an enumerator itself
if that makes sense
this is all in the context of unity coroutines
Oh didn't notice the yield was returning from a call
So essentially doing embeded coroutines?
yeah
the problem is that I don't want the main method to exit if Method2 throws an exception, but I can't simply do a try-catch around it
which has currently resulted in me using a task completion source to observe the coroutine, and running Method2 in a separate coroutine π
So if both methods are intended as coroutines, it seems strange to call it in a yield. It's essentially starting coroutine 2 every time except the return will be an enumerator rather than an expected coroutine type like WaitForSeconds
in which the try-finally is useful since I had hoped that would notify the completion source even if Method2 throws
Then you can always use try-catch to store the result and yield afterwards if no exception was thrown, otherwise skip the item
erπ
REPL Result: Success
Result: List<int>
Console Output
Compile: 499.978ms | Execution: 90.990ms | React with β to remove this embed.
dispose should be called fine
that doesn't actually work, since that will only catch any exceptions that happen before the first yield return
Design wise it also seems strange that an enumerator throwing an exception on MoveNext would still be valid to continue enumeration
I think unity may also be the problem, I thought it may be killing my coroutine on the exception
I'm not continuing the Method2 enumerator after an exception
unity shouldn't be the problem
i mean maybe
but not likely
I highly doubt unity doesn't auto dispose the enumerator after a coroutine ends
I mean, this is what I tried, and it doesn't work
we would need to know how you tried that
wdym?
how are you calling the method
Method1 is going into a
StartCoroutine
call
note that Method1 also has a yield return null;
at the start, so that control is immediately given to unityDoes Unity actually support yielding enumerators as individual items? :hmm:
(for couroutines)
yup
Strange I can't find docs on that behavior, I just assumed you'd need to manually iterate Method2 and yield each item
If that unrolling is handled by unity, then that may be another source of the problem. The Method2 call in itself doesn't actually run to the end and instead exists immediately after creating the iterator. You wouldn't be getting MoveNext exceptions until that gets unrolled
not that i can tell?
Either way a safer way of doing a try on Method2's MoveNext would be manually enumerating it from Method1 and yielding each item.
yeah, that is what I'm doing
With
yield return Method2();
or something else?specifically:
So what's wrong with this? You no longer try to dispose anything or have finally
wdym? I never said this was wrong, I explicitly said:
I think I managed to solve the problem.
Right
Then what do you mean by only working with one level? You could always rethrow the exception to have it handled by a higher level
well, if Method2 yield returns another enumerator, and that enumerator throws, my code won't work anymore
The new snippet has Method1 return items from Method2
yes
one of the items returned from Method2 can be an IEnumerator
which would then basically be equivalent to putting a
yield return Method3()
in Method1
thus causing the same issue againSo do you want the items fully flattened by the end? You can always check the type of the current object against IEnumerator and enumerate that
well they're coroutines, there aren't really any "items"
but you can think of it that way i guess
The wait requests* ig
they just want to catch any exceptions thrown by any coroutines i guess
You could also let the methods yield enumerators and only try at the highest level, then flatten recursively for every item that's an enumerator, but then you couldn't start coroutines on individual methods and woulld need to instead wrap it in a flattenner helper method.
StartCoroutine(MyCoroutine().HandleExceptions());
^
Though woudln't the internal call to HandleException need to be enumerated as well instead of yielded?
?
yield return HandleExceptions(subRoutine);
On the first level, it would send an enumerator to the coroutine rather than Wait objectsyeah it works fine actually
i think in past versions you had to wrap it in
StartCoroutine
as well
but it worksThat's what I was wondering, I couldn't find docs about unity flattening subroutines you send it
don't have any either lol
i just tested it
Although if some subroutines are known to not throw exceptions, it might be worth letting them yield from HandleExceptions rather than forcing it at the root
Or have a parameter to choose the behavior