使用 ValueTask 等待单个 .NET 事件
Awaiting a single .NET event with a ValueTask
我有一个简单的 ITimer
界面,它只有一个经典 Elapsed
.NET 事件,该事件在特定时间跨度后引发。
interface ITimer
{
void Start(TimeSpan interval);
void Stop();
public event EventHandler Elapsed;
}
现在我想创建一个类似于 Task.Delay(TimeSpan)
的功能,但它应该使用 ITimer
抽象而不是“实时”时间。也有不同的形式 Task.Delay(TimeSpan)
我虽然 return 一个 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);
}
ManualResetValueTaskSourceCore<TResult>
上写的不多。它本质上是一个 TaskCompletionSource<T>
但对于 ValueTask<T>
.
它确实打算在内部像你的计时器这样的类型中使用,但如果你想将它用作外部方法,你可以create a wrapper for it:
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
的真正目的是成为第一个 class 公民;即,理想情况下,您将更新实际的计时器实现(和接口)以在其自己的 class 中支持 ValueTask Delay(TimeSpan)
而不是外部方法。在计时器实例内部,它可以重用 ValueTask
支持类型 (ManualResetValueTaskSource<T>
),它可以知道何时调用 Reset
,并且可以完全避免 delegate/event。
我有一个简单的 ITimer
界面,它只有一个经典 Elapsed
.NET 事件,该事件在特定时间跨度后引发。
interface ITimer
{
void Start(TimeSpan interval);
void Stop();
public event EventHandler Elapsed;
}
现在我想创建一个类似于 Task.Delay(TimeSpan)
的功能,但它应该使用 ITimer
抽象而不是“实时”时间。也有不同的形式 Task.Delay(TimeSpan)
我虽然 return 一个 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);
}
ManualResetValueTaskSourceCore<TResult>
上写的不多。它本质上是一个 TaskCompletionSource<T>
但对于 ValueTask<T>
.
它确实打算在内部像你的计时器这样的类型中使用,但如果你想将它用作外部方法,你可以create a wrapper for it:
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
的真正目的是成为第一个 class 公民;即,理想情况下,您将更新实际的计时器实现(和接口)以在其自己的 class 中支持 ValueTask Delay(TimeSpan)
而不是外部方法。在计时器实例内部,它可以重用 ValueTask
支持类型 (ManualResetValueTaskSource<T>
),它可以知道何时调用 Reset
,并且可以完全避免 delegate/event。