-
Notifications
You must be signed in to change notification settings - Fork 147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add AsyncLazy<T>.Dispose()
method
#1218
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that is a nice function to be added.
Some opinions on your debate points:
I think if
I like throwing rather than returning the disposed value. You make a fair point about compatibility, but the caller will already have consciously opted into the new behavior by calling
IMHO, the behavior of these properties shouldn't change after disposal. They still return information that might be meaningful to the caller, regardless of whether the After writing all of that, I started wondering how my answers would change if the new method was called
|
I agree with @jdrobison's analysis. I like his musings on DisposeValue especially. Explicitness is good since AsyncLazy itself isn't disposable, just that the inner Value might be. |
Thank you for your feedback. In trying to apply @jdrobison's suggestions, it occurs to me that if |
Fyi, the idea of lifting this aspect of the underlying value to the lazy
itself feels pretty wrong to me. Is this something that `Lazy<>` itself
does?
Also, why would it just be this interface that was lifted in this way?
What about things like iequatable, etc.?
…On Wed, Aug 30, 2023, 14:06 Andrew Arnott ***@***.***> wrote:
Thank you for your feedback. In trying to apply @jdrobison
<https://github.com/jdrobison>'s suggestions, it occurs to me that if
DisposeValue() is called before the value factory is ever invoked, that
nothing will be disposed of, and a subsequent GetValueAsync() call will
return an undisposed value. Or we'd create it but immediately dispose of it.
IMO a more reasonable approach is to block access to the value after
disposal has been requested so there is no risk of creating a value that
should have been disposed of already had it been created.
I'm still thinking through the ramifications of what that means for the
rest of the feedback. More comments welcome.
—
Reply to this email directly, view it on GitHub
<#1218 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABC2MY7TYLP2MKW5ONUOWF3XX6TNBANCNFSM6AAAAAA2JRNQCY>
.
You are receiving this because your review was requested.Message ID:
***@***.***>
|
@CyrusNajmabadi Thanks for reviewing! The reason we lift it here and not for |
What's the difference, philosophically, between this and Lazy<>?
Also, are we lifting both IDisposable and IAsyncDisposable up to the lazy
itself?
Note: I cannot see the actual change as this is in the MS repo and I'm
locked out from that.
…On Wed, Aug 30, 2023, 14:16 Andrew Arnott ***@***.***> wrote:
@CyrusNajmabadi <https://github.com/CyrusNajmabadi> Thanks for reviewing!
The reason we lift it here and not for IEquatable<T> is because
IEquatable<T> doesn't own or create a value. AsyncLazy<T> constructs the
value and owns it forever after. And T is frequently disposable.
Implementing responsible and correct disposal of a async lazily-initialized
value is complex, and I'm trying to make that easy to get right.
—
Reply to this email directly, view it on GitHub
<#1218 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABC2MY5BNWJN7IPSPLDAFQDXX6URLANCNFSM6AAAAAA2JRNQCY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
I don't know if there is a philosophical difference between |
As for supporting |
Yeah. I think this just opens too many questions for me. I genuinely am not
sure what actual semantics make sense here. Especially for values that are
not constructed yet, but get constructed later. Or which may be in the
process of being constructed.
I'd prefer if all of this was owned by whoever owns the asynclazy itself.
…On Wed, Aug 30, 2023, 14:27 Andrew Arnott ***@***.***> wrote:
I don't know if there is a philosophical difference between Lazy<T> and
this one. But I don't own that one, and I am not aware of a common need to
dispose a value that hasn't yet been fully constructed by a Lazy<T>
either. I'm just looking at usage patterns of AsyncLazy<T> and see a need
to address.
—
Reply to this email directly, view it on GitHub
<#1218 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABC2MYZFEVNKNSL4CIRQXKDXX6V5PANCNFSM6AAAAAA2JRNQCY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
I'm not sure what you mean by that. |
I mean: Whoever owns the asynclazy instance can decide what to do based on
the existing exposed API. Similar to what they would have to do if this was
Lazy<>.
I don't like idea of exposing this off asynclazy because it makes it less
tenable to easily move to/from Lazy/AsyncLazy if it turns out my
computation gets changed to sync/async as my component develops.
Anyway, my 2c. It's your API :-)
…On Wed, Aug 30, 2023, 14:34 Andrew Arnott ***@***.***> wrote:
I'd prefer if all of this was owned by whoever owns the asynclazy itself.
I'm not sure what you mean by that.
I own the class. And the behavior isn't changing unless the owner of the
instance opts in by calling the new DisposeValue method.
—
Reply to this email directly, view it on GitHub
<#1218 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABC2MY7U3G2HEHH4XZ2JLIDXX6WUXANCNFSM6AAAAAA2JRNQCY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
It's a fair thought, but since roslyn's |
02440b6
to
6222545
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a comment, as I still feel it is necessary to add an extra MemoryBarrier inside the code in DisposeValueAsync side.
/// <summary> | ||
/// A value set on the <see cref="value"/> field when this object is disposed. | ||
/// </summary> | ||
private static readonly Task<T> DisposedSentinel = Task.FromException<T>(new ObjectDisposedException(nameof(AsyncLazy<T>))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if nameof(AsyncLazy<T>)
is now the right parameter for the ObjectDisposedException
, since that's not what's disposed. However, if you tried to smuggle something like nameof(T)
into the exception, you'd have a different exception for each T
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... At the moment I'm leaning on going ahead with the AsyncLazy value, since that's what's throwing. It may be odd to throw ODE from AsyncLazy but with a type name that belongs to something else.
Is the idea that by calling this, a bit is set that indicates that if the
value is ever created in the future that it be disposed? So that way there
is a guarantee that if ever created it will always be certain to be
disposed, and if so that happens at most once?
With a lock around the bit and marking the value computed, it feels like
this guarantee could be provided.
If you'd isn't the guarantee, what are the samantics around asking for the
value to be disposed?
…On Wed, Aug 30, 2023, 15:29 Lifeng Lu ***@***.***> wrote:
***@***.**** approved this pull request.
I left a comment, as I still feel it is necessary to add an extra
MemoryBarrier inside the code in DisposeValueAsync side.
—
Reply to this email directly, view it on GitHub
<#1218 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABC2MY4AELJ2ZECLOEKVMW3XX65E7ANCNFSM6AAAAAA2JRNQCY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
6222545
to
07f6843
Compare
@CyrusNajmabadi The idea is to make it easy for the AsyncLazy owner to ensure the value is disposed if it were ever created. But it also blocks creation of the value if the value factory hasn't been invoked by that point. |
This addresses a common need for a class with an `AsyncLazy<T>` field where the value is disposable to ensure on the class disposal that it can dispose of the value if and when it is constructed.
07f6843
to
0e383a3
Compare
This addresses a common need for a class with an
AsyncLazy<T>
field where the value is disposable to ensure on the class disposal that it can dispose of the value if and when it is constructed.Problem to be solved
To simplify and optimize a common (and often improperly done) pattern of disposing the value, that would typically appear like this:
The problems with the pattern above include:
IAsyncDisposable
owners could avoid the sync block)IsValueFactoryCompleted
, which abandons an undisposed value that has begun construction but isn't done.Solution
Add a simple
Dispose()
method on theAsyncLazy<T>
class itself that does the right thing, in the most efficient way possible:ObjectDisposedException
.Some important design decisions apply and are worth debate prior to merging:
AsyncLazy<T>
itself implementIDisposable
?I'm leaning on 'no' because that may trigger analyzers to force everyone to dispose of their
AsyncLazy<T>
objects, even when the constructed value does not itself implementIDisposable
.GetValue
andGetValueAsync
methods throwObjectDisposedException
afterDispose
is called, or should they just return the disposed value?I'm leaning toward throwing at the moment, but returning the disposed value will resemble the existing behavior of folks who are currently disposing of the value by hand. But returning it would mean the value is constructed, only to be disposed of, possibly amplifying race conditions with disposal and a caller's first use of the value. I am on the fence on this one.
IsValueCreated
andIsValueFactoryCompleted
return after disposal? Should it vary based on whether the value factory has actually started/completed?IsDisposed
property?Default answer is 'no'.
Alternative design
Expose this as an extension method on
AsyncLazy<T>
that only appears whenT : IDisposable
. That would be great, except that quite oftenT
doesn't derive fromIDisposable
although the constructed value may implementIDisposable
. A simple example of this is brokered service proxies, which tend to be declared simply asIFoo
but the concrete type also implementsIDisposable
. As this is a fairly common scenario, I decided to expose theDispose
method regardless ofT
.