StephenCleary / AsyncEx

A helper library for async/await.
MIT License
3.49k stars 358 forks source link

[Discussion] Return `ValueTask` instead of `AwaitableDisposable`? #265

Open BalassaMarton opened 1 year ago

BalassaMarton commented 1 year ago

Methods returning AwaitableDisposable could return ValueTask instead.

Pros

Cons

I'll submit an experimental PR just for the sake of discussion.

modmynitro commented 1 year ago

Could the System.Threading.Tasks.Extensions nuget package be used to support older framworks.

NotTsunami commented 5 months ago

Could the System.Threading.Tasks.Extensions nuget package be used to support older framworks.

Yes, it should be possible to use ValueTask from System.Threading.Tasks.Extensions to support older frameworks.

Pretty late to the discussion, but this PR as a whole would be pretty interesting to expand on.

BalassaMarton commented 5 months ago

Created a new experimental async lock implementation that supports fast, zero-alloc reentrancy: https://github.com/BalassaMarton/AsyncExtensions I'd be happy to see your feedback, especially from @StephenCleary

timcassell commented 5 months ago

@BalassaMarton I'm always skeptical of any re-entrant async locks. Does your solution hold up to the problems presented in this article? I even tried my hand at the problem myself, but abandoned the idea. (But I do have an alloc-free non-re-entrant async lock in my library.)

BalassaMarton commented 4 months ago

@timcassell look at this example: https://github.com/BalassaMarton/AsyncExtensions?tab=readme-ov-file#example-recursive-async-locking-using-a-token The specific problem I was trying to solve: I had a bunch of short, specific methods that needed exclusive access to resources, but it was hard to follow which methods were doing the actual locking vs assuming the lock being already taken. With this approach, the method parameter effectively documents that it will asynchronously lock on the resource if it's not locked already:

async ValueTask OuterMethod(AsyncLockToken token = default)
{
    using (token = await mutex.LockAsync(token))
    {
        await InnerMethod(token);
    }
}

async ValueTask InnerMethod(AsyncLockToken token = default)
{
    using (await mutex.LockAsync(token))
    {
        // ...
    }
}

I even had a variation where you could name your AsyncLock objects and annotate any method that will lock on them like [LocksOn("lockName")] - and LockAsync would validate that methods in the current call stack are correctly annotated. This is of course all experimental, I'm not saying any of this is the way to do async locking.