-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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 Dispose to IAsyncStateMachine #48482
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: Issue DetailsBackground and MotivationUnlike most exceptions, cancelations should be considered to be normal program flow. It is considered bad practice to use exceptions to control program flow, yet that is exactly how the Not only that, but throwing an OperationCanceledException is expensive. UniTask provides a means of suppressing the exception when awaiting a task to increase performance, but because of the limitations of AsyncMethodBuilder, it is impossible to cancel the So what's the solution? Proposed APIAdd public interface IAsyncStateMachine : IDisposable
{
void MoveNext();
void SetStateMachine(IAsyncStateMachine stateMachine);
} Doing so would allow a custom AsyncMethodBuilder to short-circuit the statemachine, perform any necessary cleanup, and cancel the task without throwing an exception. Usage ExampleAsync function public async Promise<int> Func(CancellationToken token)
{
try
{
if (token.IsCancellationRequested)
{
await new CancelAsyncPromise(); // Cancel the promise without throwing.
throw new Exception("This should never be reached");
}
return 100;
}
finally
{
Console.WriteLine("Finally was executed.");
}
} Short-circuit awaitable public struct CancelAsyncPromise : INotifyCompletion
{
public CancelAsyncPromise GetAwaiter() => this;
public bool IsCompleted => false;
public void OnCompleted(Action continuation) => throw new NotImplementedException();
public void GetResult() => throw new NotImplementedException();
} Custom AsyncMethodBuilder implementation public struct PromiseMethodBuilder<T>
{
private AsyncPromiseRef _promise;
public Promise<T> Task
{
get { return new Promise<T>(_promise, _promise.Id); }
}
public static PromiseMethodBuilder<T> Create()
{
return new PromiseMethodBuilder<T>()
{
_promise = AsyncPromiseRef.GetOrCreate()
};
}
public void SetException(Exception exception)
{
if (exception is OperationCanceledException)
{
_promise.SetCanceled();
}
else
{
_promise.SetException(exception);
}
}
public void SetResult(T result)
{
_promise.SetResult(result);
}
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine
{
if (awaiter is CancelAsyncPromise)
{
try
{
stateMachine.Dispose();
_promise.SetCanceled();
}
catch (Exception e)
{
SetException(e);
}
}
else
{
awaiter.OnCompleted(_promise.GetContinuation(ref stateMachine));
}
}
} Alternative APIOr more like an additional nice-to-have, instead of the awaitable implementing public void Cancel<TCanceler>(ref TCanceler canceler, ref TStateMachine stateMachine)
where TCanceler : IAsyncCanceler
where TStateMachine : IAsyncStateMachine
{
try
{
stateMachine.Dispose();
_promise.SetCanceled();
}
catch (Exception e)
{
SetException(e);
}
} public struct CancelAsyncPromise : IAsyncCanceler { } Although, that might not be feasible if a type can implement
|
The C# compiler is providing the implementation of IAsyncStateMachine... what would its generated Dispose method do? Are you suggesting Dispose would execute any finally blocks as if an exception had been thrown but without one actually being thrown? |
@stephentoub Yes, that's exactly right. Currently, if I try to short-circuit the async method this way, any finally blocks would not be executed. |
I actually didn't consider awaiting something within the finally block, so I'll close this issue and start a discussion in csharplang. |
Background and Motivation
Unlike most exceptions, cancelations should be considered to be normal program flow. It is considered bad practice to use exceptions to control program flow, yet that is exactly how the
async Task
API was architected.Not only that, but throwing an OperationCanceledException is expensive. UniTask provides a means of suppressing the exception when awaiting a task to increase performance, but because of the limitations of AsyncMethodBuilder, it is impossible to cancel the
async UniTask
from within the async function without throwing the exception. The only workaround is to rejigger the return type to something likeasync UniTask<(int result, bool canceled)>
which makes it cumbersome to work with, both inside the async function and outside.So what's the solution?
Proposed API
Add
void Dispose()
toIAsyncStateMachine
.Doing so would allow a custom AsyncMethodBuilder to short-circuit the statemachine, perform any necessary cleanup, and cancel the task without throwing an exception.
Usage Example
Async function
Short-circuit awaitable
Custom AsyncMethodBuilder implementation
Alternative API
Or more like an additional nice-to-have, instead of the awaitable implementing
INotifyCompletion
andIsCompleted => false;
and the MethodBuilder checking the type inAwaitOnCompleted
, add a new optional method to AsyncMethodBuilders to accept a cancelation type:Although, that might not be feasible if a type can implement
IAsyncCanceler
andINotifyCompletion
, unless a new keyword is introduced.The text was updated successfully, but these errors were encountered: