我有一个简单的ITimer接口,它只有一个经典的Elapsed .NET事件,这个事件是在某个时间段之后引发的。
interface ITimer
{
void Start(TimeSpan interval);
void Stop();
public event EventHandler Elapsed;
}现在,我想创建一个类似于Task.Delay(TimeSpan)的功能,但是它应该使用ITimer抽象,而不是“实时”。另外,与Task.Delay(TimeSpan)不同的是,我认为返回一个ValueTask而不是一个Task可能是个好主意,因为它可以实现无分配,而且通常性能更好。而且,无论如何,完成延迟只需要等待一次。
现在,一个有点幼稚的实现可能会像这样。这里的问题是,根本没有使用ValueTask的好处。一个像这样的实现怎么会充分受益于ValueTask的承诺(廉价和免费分配)。
ValueTask Delay(TimeSpan duration, CancellationToken cancellationToken)
{
// We get the ITimer instance from somwhere, doesn't matter for this question.
ITimer timer = timerFactory.Create();
var tcs = new TaskCompletionSource<bool>();
timer.Elapsed += (_, __) => { tcs.SetResult(true); };
cancellationToken.Register(() =>
{
timer.Stop();
throw new OperationCanceledException();
});
timer.Start(duration);
return new ValueTask(tcs.Task);
}发布于 2021-02-26 14:51:01
在ManualResetValueTaskSourceCore<TResult>上写的不多。它本质上是一个TaskCompletionSource<T>,但是对于ValueTask<T>来说。
它实际上是打算在诸如计时器这样的类型中使用,但是如果您想要将它用作外部方法,则可以使用为它创建一个包装器:
public sealed class ManualResetValueTaskSource<T> : IValueTaskSource<T>, IValueTaskSource
{
private ManualResetValueTaskSourceCore<T> _logic; // mutable struct; do not make this readonly
public bool RunContinuationsAsynchronously
{
get => _logic.RunContinuationsAsynchronously;
set => _logic.RunContinuationsAsynchronously = value;
}
public void Reset() => _logic.Reset();
public void SetResult(T result) => _logic.SetResult(result);
public void SetException(Exception error) => _logic.SetException(error);
short IValueTaskSource.Version => _logic.Version;
short IValueTaskSource<T>.Version => _logic.Version;
void IValueTaskSource.GetResult(short token) => _logic.GetResult(token);
T IValueTaskSource<T>.GetResult(short token) => _logic.GetResult(token);
ValueTaskSourceStatus IValueTaskSource.GetStatus(short token) => _logic.GetStatus(token);
ValueTaskSourceStatus IValueTaskSource<T>.GetStatus(short token) => _logic.GetStatus(token);
void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _logic.OnCompleted(continuation, state, token, flags);
void IValueTaskSource<T>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _logic.OnCompleted(continuation, state, token, flags);
}然后,您可以这样使用它(为了简单起见删除了取消):
ValueTask Delay(TimeSpan duration)
{
ITimer timer = timerFactory.Create();
var vts = new ManualResetValueTaskSource<object>();
TimerElapsed eventHandler = null;
eventHandler = (_, __) =>
{
timer.Elapsed -= eventHandler;
vts.SetResult(null);
};
timer.Elapsed += eventHandler;
timer.Start(duration);
return new ValueTask(vts);
}注意,由于这是一个外部方法,所以仍然需要分配ManualResetValueTaskSource<T> (以及委托)。所以这并不能买到任何东西,我会选择在这里使用TaskCompletionSource<T>。当您添加CancellationToken支持并且必须处理计时器触发(vts.SetResult)和取消(vts.SetException)之间的竞争条件时,情况尤其如此,因为我非常肯定每个ValueTask操作中只有一个是这样的,而且在我们的ManualResetValueTaskSource<T>中没有像TaskCompletionSource<T>那样的Try*变体。
ValueTask实际上更像是一个一流的公民;也就是说,理想情况下,您应该更新实际的计时器实现(和接口),以便在自己的类中而不是在外部方法中支持ValueTask Delay(TimeSpan)。在计时器实例中,它可以重用ValueTask支持类型(ManualResetValueTaskSource<T>),它可以知道何时调用Reset,并且可以完全避免委托/事件。
https://stackoverflow.com/questions/66387225
复制相似问题