C# 将ManualResetEvent包装为可等待的任务
我想等待一个带有超时和观察取消的手动重置事件。我想出了下面这样的办法。手动重置事件对象由我无法控制的API提供。有没有一种方法可以在不占用和阻止线程池中的线程的情况下实现这一点C# 将ManualResetEvent包装为可等待的任务,c#,.net,multithreading,task-parallel-library,async-await,C#,.net,Multithreading,Task Parallel Library,Async Await,我想等待一个带有超时和观察取消的手动重置事件。我想出了下面这样的办法。手动重置事件对象由我无法控制的API提供。有没有一种方法可以在不占用和阻止线程池中的线程的情况下实现这一点 static Task<bool> TaskFromWaitHandle(WaitHandle mre, int timeout, CancellationToken ct) { return Task.Run(() => { bool s = WaitHandle.Wa
static Task<bool> TaskFromWaitHandle(WaitHandle mre, int timeout, CancellationToken ct)
{
return Task.Run(() =>
{
bool s = WaitHandle.WaitAny(new WaitHandle[] { mre, ct.WaitHandle }, timeout) == 0;
ct.ThrowIfCancellationRequested();
return s;
}, ct);
}
// ...
if (await TaskFromWaitHandle(manualResetEvent, 1000, cts.Token))
{
// true if event was set
}
else
{
// false if timed out, exception if cancelled
}
来自WaitHandle的静态任务(WaitHandle mre、int超时、CancellationToken ct)
{
返回任务。运行(()=>
{
bool s=WaitHandle.WaitAny(新的WaitHandle[]{mre,ct.WaitHandle},超时)=0;
ct.ThrowIfCancellationRequested();
返回s;
},ct);
}
// ...
if(等待来自WaitHandle的任务(manualResetEvent,1000,cts.Token))
{
//如果设置了事件,则为true
}
其他的
{
//超时时为false,取消时为exception
}
[编辑]显然,使用
RegisterWaitForSingleObject
是必要的。我将尝试一下。RegisterWaitForSingleObject
将等待合并到专用的服务员线程上,每个线程都可以在多个句柄上等待(具体来说,其中63个是最大等待对象数
减去一个“控制”句柄数)
因此,您应该能够使用如下内容(警告:未测试):
公共静态类WaitHandleExtensions
{
公共静态任务AsTask(此WaitHandle句柄)
{
返回AsTask(句柄,超时.InfiniteTimeSpan);
}
公共静态任务AsTask(此WaitHandle句柄,TimeSpan超时)
{
var tcs=new TaskCompletionSource();
var registration=ThreadPool.RegisterWaitForSingleObject(句柄,(状态,timedOut)=>
{
var localTcs=(TaskCompletionSource)状态;
if(timedOut)
localTcs.TrySetCanceled();
其他的
localTcs.TrySetResult(null);
},tcs,timeout,executeOnOnce:true);
tcs.Task.ContinueWith((u,state)=>((RegisteredWaitHandle)state.Unregister(null),registration,TaskScheduler.Default);
返回tcs.Task;
}
}
您还可以使用信号量lim.WaitAsync(),它类似于ManualResetEvent您可以试一试,尝试在示例的基础上支持超时和取消
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// An async manual reset event.
/// </summary>
public sealed class ManualResetEventAsync
{
// Inspiration from https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-1-asyncmanualresetevent/
// and the .net implementation of SemaphoreSlim
/// <summary>
/// The timeout in milliseconds to wait indefinitly.
/// </summary>
private const int WaitIndefinitly = -1;
/// <summary>
/// True to run synchronous continuations on the thread which invoked Set. False to run them in the threadpool.
/// </summary>
private readonly bool runSynchronousContinuationsOnSetThread = true;
/// <summary>
/// The current task completion source.
/// </summary>
private volatile TaskCompletionSource<bool> completionSource = new TaskCompletionSource<bool>();
/// <summary>
/// Initializes a new instance of the <see cref="ManualResetEventAsync"/> class.
/// </summary>
/// <param name="isSet">True to set the task completion source on creation.</param>
public ManualResetEventAsync(bool isSet)
: this(isSet: isSet, runSynchronousContinuationsOnSetThread: true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ManualResetEventAsync"/> class.
/// </summary>
/// <param name="isSet">True to set the task completion source on creation.</param>
/// <param name="runSynchronousContinuationsOnSetThread">If you have synchronous continuations, they will run on the thread which invokes Set, unless you set this to false.</param>
public ManualResetEventAsync(bool isSet, bool runSynchronousContinuationsOnSetThread)
{
this.runSynchronousContinuationsOnSetThread = runSynchronousContinuationsOnSetThread;
if (isSet)
{
this.completionSource.TrySetResult(true);
}
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <returns>A task which completes when the event is set.</returns>
public Task WaitAsync()
{
return this.AwaitCompletion(ManualResetEventAsync.WaitIndefinitly, default(CancellationToken));
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <param name="token">A cancellation token.</param>
/// <returns>A task which waits for the manual reset event.</returns>
public Task WaitAsync(CancellationToken token)
{
return this.AwaitCompletion(ManualResetEventAsync.WaitIndefinitly, token);
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <param name="timeout">A timeout.</param>
/// <param name="token">A cancellation token.</param>
/// <returns>A task which waits for the manual reset event. Returns true if the timeout has not expired. Returns false if the timeout expired.</returns>
public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken token)
{
return this.AwaitCompletion((int)timeout.TotalMilliseconds, token);
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <param name="timeout">A timeout.</param>
/// <returns>A task which waits for the manual reset event. Returns true if the timeout has not expired. Returns false if the timeout expired.</returns>
public Task<bool> WaitAsync(TimeSpan timeout)
{
return this.AwaitCompletion((int)timeout.TotalMilliseconds, default(CancellationToken));
}
/// <summary>
/// Set the completion source.
/// </summary>
public void Set()
{
if (this.runSynchronousContinuationsOnSetThread)
{
this.completionSource.TrySetResult(true);
}
else
{
// Run synchronous completions in the thread pool.
Task.Run(() => this.completionSource.TrySetResult(true));
}
}
/// <summary>
/// Reset the manual reset event.
/// </summary>
public void Reset()
{
// Grab a reference to the current completion source.
var currentCompletionSource = this.completionSource;
// Check if there is nothing to be done, return.
if (!currentCompletionSource.Task.IsCompleted)
{
return;
}
// Otherwise, try to replace it with a new completion source (if it is the same as the reference we took before).
Interlocked.CompareExchange(ref this.completionSource, new TaskCompletionSource<bool>(), currentCompletionSource);
}
/// <summary>
/// Await completion based on a timeout and a cancellation token.
/// </summary>
/// <param name="timeoutMS">The timeout in milliseconds.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>A task (true if wait succeeded). (False on timeout).</returns>
private async Task<bool> AwaitCompletion(int timeoutMS, CancellationToken token)
{
// Validate arguments.
if (timeoutMS < -1 || timeoutMS > int.MaxValue)
{
throw new ArgumentException("The timeout must be either -1ms (indefinitely) or a positive ms value <= int.MaxValue");
}
CancellationTokenSource timeoutToken = null;
// If the token cannot be cancelled, then we dont need to create any sort of linked token source.
if (false == token.CanBeCanceled)
{
// If the wait is indefinite, then we don't need to create a second task at all to wait on, just wait for set.
if (timeoutMS == -1)
{
return await this.completionSource.Task;
}
timeoutToken = new CancellationTokenSource();
}
else
{
// A token source which will get canceled either when we cancel it, or when the linked token source is canceled.
timeoutToken = CancellationTokenSource.CreateLinkedTokenSource(token);
}
using (timeoutToken)
{
// Create a task to account for our timeout. The continuation just eats the task cancelled exception, but makes sure to observe it.
Task delayTask = Task.Delay(timeoutMS, timeoutToken.Token).ContinueWith((result) => { var e = result.Exception; }, TaskContinuationOptions.ExecuteSynchronously);
var resultingTask = await Task.WhenAny(this.completionSource.Task, delayTask).ConfigureAwait(false);
// The actual task finished, not the timeout, so we can cancel our cancellation token and return true.
if (resultingTask != delayTask)
{
// Cancel the timeout token to cancel the delay if it is still going.
timeoutToken.Cancel();
return true;
}
// Otherwise, the delay task finished. So throw if it finished because it was canceled.
token.ThrowIfCancellationRequested();
return false;
}
}
}
使用系统;
使用系统线程;
使用System.Threading.Tasks;
///
///异步手动重置事件。
///
公共密封类ManualResetEventAsync
{
//灵感来自https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-1-asyncmanualresetevent/
//以及信号量LIM的.net实现
///
///不确定等待的超时(以毫秒为单位)。
///
private const int waitindefinity=-1;
///
///True在调用Set的线程上运行同步continuations。False在线程池中运行同步continuations。
///
私有只读bool runSynchronousContinuationsOnSetThread=true;
///
///当前任务完成源。
///
私有易失性TaskCompletionSource completionSource=新TaskCompletionSource();
///
///初始化类的新实例。
///
///True可在创建时设置任务完成源。
公共手册重置事件异步(布尔isSet)
:此(isSet:isSet,runSynchronousContinuationsOnSetThread:true)
{
}
///
///初始化类的新实例。
///
///True可在创建时设置任务完成源。
///如果您有同步延续,它们将在调用Set的线程上运行,除非您将其设置为false。
公共手册ResetEventAsync(bool isSet,bool runSynchronousContinuationsOnSetThread)
{
this.runSynchronousContinuationsOnSetThread=runSynchronousContinuationsOnSetThread;
如果(isSet)
{
this.completionSource.TrySetResult(true);
}
}
///
///等待手动重置事件。
///
///设置事件时完成的任务。
公共任务WaitAsync()
{
返回此.waitCompletion(ManualResetEventAsync.WaitIndefinity,默认值(CancellationToken));
}
///
///等待手动重置事件。
///
///取消代币。
///等待手动重置事件的任务。
公共任务WaitAsync(CancellationToken令牌)
{
返回此.waitCompletion(ManualResetEventAsync.WaitIndefinity,令牌);
}
///
///等待手动重置事件。
///
///暂停。
///取消代币。
///等待手动重置事件的任务。如果超时未过期,则返回true。如果超时已过期,则返回false。
公共任务WaitAsync(TimeSpan超时,CancellationToken令牌)
{
返回此.AwaitCompletion((int)timeout.total毫秒,令牌);
}
///
///等待手动重置事件。
///
///暂停。
///等待手动重置事件的任务。如果超时未过期,则返回true。如果超时已过期,则返回false。
公共任务WaitAsync(TimeSpan超时)
{
返回此.AwaitCompletion((int)timeout.total毫秒,默认值(CancellationToken));
}
///
///设置完成源。
///
公共无效集()
{
if(this.runSynchronousContinuationsOnSetThread)
{
this.completionSource.TrySetResult(true);
}
其他的
{
//在线程池中运行同步完成。
Task.Run(()=>this.completionSource.TrySetResult(true));
}
}
///
///重置手动重置事件。
///
公共无效重置()
{
//获取对当前完成源的引用。
var currentCompletionSource=this.completionSource;
//检查是否没有什么要做的,返回。
如果(!currentCompletionSource.Task.IsCompleted)
{
返回;
}
//否则,尝试用新的补全替换它,以便
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// An async manual reset event.
/// </summary>
public sealed class ManualResetEventAsync
{
// Inspiration from https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-1-asyncmanualresetevent/
// and the .net implementation of SemaphoreSlim
/// <summary>
/// The timeout in milliseconds to wait indefinitly.
/// </summary>
private const int WaitIndefinitly = -1;
/// <summary>
/// True to run synchronous continuations on the thread which invoked Set. False to run them in the threadpool.
/// </summary>
private readonly bool runSynchronousContinuationsOnSetThread = true;
/// <summary>
/// The current task completion source.
/// </summary>
private volatile TaskCompletionSource<bool> completionSource = new TaskCompletionSource<bool>();
/// <summary>
/// Initializes a new instance of the <see cref="ManualResetEventAsync"/> class.
/// </summary>
/// <param name="isSet">True to set the task completion source on creation.</param>
public ManualResetEventAsync(bool isSet)
: this(isSet: isSet, runSynchronousContinuationsOnSetThread: true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ManualResetEventAsync"/> class.
/// </summary>
/// <param name="isSet">True to set the task completion source on creation.</param>
/// <param name="runSynchronousContinuationsOnSetThread">If you have synchronous continuations, they will run on the thread which invokes Set, unless you set this to false.</param>
public ManualResetEventAsync(bool isSet, bool runSynchronousContinuationsOnSetThread)
{
this.runSynchronousContinuationsOnSetThread = runSynchronousContinuationsOnSetThread;
if (isSet)
{
this.completionSource.TrySetResult(true);
}
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <returns>A task which completes when the event is set.</returns>
public Task WaitAsync()
{
return this.AwaitCompletion(ManualResetEventAsync.WaitIndefinitly, default(CancellationToken));
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <param name="token">A cancellation token.</param>
/// <returns>A task which waits for the manual reset event.</returns>
public Task WaitAsync(CancellationToken token)
{
return this.AwaitCompletion(ManualResetEventAsync.WaitIndefinitly, token);
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <param name="timeout">A timeout.</param>
/// <param name="token">A cancellation token.</param>
/// <returns>A task which waits for the manual reset event. Returns true if the timeout has not expired. Returns false if the timeout expired.</returns>
public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken token)
{
return this.AwaitCompletion((int)timeout.TotalMilliseconds, token);
}
/// <summary>
/// Wait for the manual reset event.
/// </summary>
/// <param name="timeout">A timeout.</param>
/// <returns>A task which waits for the manual reset event. Returns true if the timeout has not expired. Returns false if the timeout expired.</returns>
public Task<bool> WaitAsync(TimeSpan timeout)
{
return this.AwaitCompletion((int)timeout.TotalMilliseconds, default(CancellationToken));
}
/// <summary>
/// Set the completion source.
/// </summary>
public void Set()
{
if (this.runSynchronousContinuationsOnSetThread)
{
this.completionSource.TrySetResult(true);
}
else
{
// Run synchronous completions in the thread pool.
Task.Run(() => this.completionSource.TrySetResult(true));
}
}
/// <summary>
/// Reset the manual reset event.
/// </summary>
public void Reset()
{
// Grab a reference to the current completion source.
var currentCompletionSource = this.completionSource;
// Check if there is nothing to be done, return.
if (!currentCompletionSource.Task.IsCompleted)
{
return;
}
// Otherwise, try to replace it with a new completion source (if it is the same as the reference we took before).
Interlocked.CompareExchange(ref this.completionSource, new TaskCompletionSource<bool>(), currentCompletionSource);
}
/// <summary>
/// Await completion based on a timeout and a cancellation token.
/// </summary>
/// <param name="timeoutMS">The timeout in milliseconds.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>A task (true if wait succeeded). (False on timeout).</returns>
private async Task<bool> AwaitCompletion(int timeoutMS, CancellationToken token)
{
// Validate arguments.
if (timeoutMS < -1 || timeoutMS > int.MaxValue)
{
throw new ArgumentException("The timeout must be either -1ms (indefinitely) or a positive ms value <= int.MaxValue");
}
CancellationTokenSource timeoutToken = null;
// If the token cannot be cancelled, then we dont need to create any sort of linked token source.
if (false == token.CanBeCanceled)
{
// If the wait is indefinite, then we don't need to create a second task at all to wait on, just wait for set.
if (timeoutMS == -1)
{
return await this.completionSource.Task;
}
timeoutToken = new CancellationTokenSource();
}
else
{
// A token source which will get canceled either when we cancel it, or when the linked token source is canceled.
timeoutToken = CancellationTokenSource.CreateLinkedTokenSource(token);
}
using (timeoutToken)
{
// Create a task to account for our timeout. The continuation just eats the task cancelled exception, but makes sure to observe it.
Task delayTask = Task.Delay(timeoutMS, timeoutToken.Token).ContinueWith((result) => { var e = result.Exception; }, TaskContinuationOptions.ExecuteSynchronously);
var resultingTask = await Task.WhenAny(this.completionSource.Task, delayTask).ConfigureAwait(false);
// The actual task finished, not the timeout, so we can cancel our cancellation token and return true.
if (resultingTask != delayTask)
{
// Cancel the timeout token to cancel the delay if it is still going.
timeoutToken.Cancel();
return true;
}
// Otherwise, the delay task finished. So throw if it finished because it was canceled.
token.ThrowIfCancellationRequested();
return false;
}
}
}
ManualResetEvent SomePublicSignal = new ManualResetEvent();
...
...
// Only thing this task does is wait for the signal.
await Task.Run(() => { SomePublicSignal.WaitOne(); });
...
...
// Then can use in a Task.WaitAny(....)
Task.WaitAny(
new Task[] {
Task.Run(() => { SomePublicSignal.WaitOne(); }),
Task.Delay(200, stoppingToken) } );