这是我用于测试的一个类,支持CancellationToken。
此测试方法向我们展示了等待
ClassWithEvent 的
MyEvent 实例被触发。
public async Task TestEventAwaiter()
这是事件等待器类。
public class EventAwaiter<TOwner>
{
private readonly TOwner_owner;
private readonly string _eventName;
private readonly TaskCompletionSource<bool> _taskCompletionSource;
private readonly CancellationTokenSource _elapsedCancellationTokenSource;
private readonly CancellationTokenSource _linkedCancellationTokenSource;
private readonly CancellationToken _activeCancellationToken;
private Delegate _localHookDelegate;
private EventInfo _eventInfo;
public static Task<bool> RunAsync(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
return (new EventAwaiter<TOwner>(owner, eventName, timeout, cancellationToken)).RunAsync(timeout);
}
private EventAwaiter(
TOwner owner,
string eventName,
TimeSpan timeout,
CancellationToken? cancellationToken = null)
{
if (owner == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(owner)));
if (eventName == null) throw new TypeInitializationException(this.GetType().FullName, new ArgumentNullException(nameof(eventName)));
_owner = owner;
_eventName = eventName;
_taskCompletionSource = new TaskCompletionSource<bool>();
_elapsedCancellationTokenSource = new CancellationTokenSource();
_linkedCancellationTokenSource =
cancellationToken == null
? null
: CancellationTokenSource.CreateLinkedTokenSource(_elapsedCancellationTokenSource.Token, cancellationToken.Value);
_activeCancellationToken = (_linkedCancellationTokenSource ?? _elapsedCancellationTokenSource).Token;
_eventInfo = typeof(TOwner).GetEvent(_eventName);
Type eventHandlerType = _eventInfo.EventHandlerType;
MethodInfo invokeMethodInfo = eventHandlerType.GetMethod("Invoke");
var parameterTypes = Enumerable.Repeat(this.GetType(),1).Concat(invokeMethodInfo.GetParameters().Select(p => p.ParameterType)).ToArray();
DynamicMethod eventRedirectorMethod = new DynamicMethod("EventRedirect", typeof(void), parameterTypes);
ILGenerator generator = eventRedirectorMethod.GetILGenerator();
generator.Emit(OpCodes.Nop);
generator.Emit(OpCodes.Ldarg_0);
generator.EmitCall(OpCodes.Call, this.GetType().GetMethod(nameof(OnEventRaised),BindingFlags.Public | BindingFlags.Instance), null);
generator.Emit(OpCodes.Ret);
_localHookDelegate = eventRedirectorMethod.CreateDelegate(eventHandlerType,this);
}
private void AddHandler()
{
_eventInfo.AddEventHandler(_owner, _localHookDelegate);
}
private void RemoveHandler()
{
_eventInfo.RemoveEventHandler(_owner, _localHookDelegate);
}
private Task<bool> RunAsync(TimeSpan timeout)
{
AddHandler();
Task.Delay(timeout, _activeCancellationToken).
ContinueWith(TimeOutTaskCompleted);
return _taskCompletionSource.Task;
}
private void TimeOutTaskCompleted(Task tsk)
{
RemoveHandler();
if (_elapsedCancellationTokenSource.IsCancellationRequested) return;
if (_linkedCancellationTokenSource?.IsCancellationRequested == true)
SetResult(TaskResult.Cancelled);
else if (!_taskCompletionSource.Task.IsCompleted)
SetResult(TaskResult.Failed);
}
public void OnEventRaised()
{
RemoveHandler();
if (_taskCompletionSource.Task.IsCompleted)
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
}
else
{
if (!_elapsedCancellationTokenSource.IsCancellationRequested)
_elapsedCancellationTokenSource?.Cancel(false);
SetResult(TaskResult.Success);
}
}
enum TaskResult { Failed, Success, Cancelled }
private void SetResult(TaskResult result)
{
if (result == TaskResult.Success)
_taskCompletionSource.SetResult(true);
else if (result == TaskResult.Failed)
_taskCompletionSource.SetResult(false);
else if (result == TaskResult.Cancelled)
_taskCompletionSource.SetCanceled();
Dispose();
}
public void Dispose()
{
RemoveHandler();
_elapsedCancellationTokenSource?.Dispose();
_linkedCancellationTokenSource?.Dispose();
}
}
它基本上依赖于CancellationTokenSource来报告结果。
它使用一些IL注入来创建与事件签名匹配的委托。
然后,使用一些反射将该委托添加为该事件的处理程序。
生成方法的主体只是调用EventAwaiter类上的另一个函数,然后使用CancellationTokenSource报告成功。
注意,不要直接在产品中使用此代码。这只是一个工作示例。
例如,IL生成是一个昂贵的过程。您应该避免重复生成相同的方法,而是缓存这些方法。
ManualResetEvent(Slim)
似乎不支持WaitAsync()
。 - svickasync
并不意味着“在不同的线程上运行”或类似的东西。它只是意味着“你可以在这个方法中使用await
”。在这种情况下,在GetResults()
内部阻塞实际上会阻塞UI线程。 - svickSemaphoreSlim.WaitAsync
不仅仅是将Wait
推送到线程池线程上。SemaphoreSlim
有一个适当的Task
队列,用于实现WaitAsync
。 - Stephen Cleary