如何制作非阻塞等待句柄?
How to make a nonblocking wait handle?
本质上,我正在做的是创建一个网络服务器来处理 API 调用,然后在完成后继续执行方法,所以本质上:
new WebServer(myAutoResetEvent);
myAutoResetEvent.WaitOne();
但是,这会阻塞线程直到那时。有没有办法使这个异步?将它包装在 await Task.Run()
调用中是否可以,即 await Task.Run(() => myAutoResetEvent.WaitOne())
?
谢谢!
通常,WebServer
ctor 不应该做任何有趣的事情。应该有一个运行服务器的 Task WebServer.RunAsync
函数。然后,您可以使用生成的任务进行同步和协调。
如果您不想这样,您可以将 TaskCompletionSource<object>
用作 one-shot async-ready 事件。
我相信 ThreadPool
class 有一种有效等待 WaitHandle
设置的方法,但这是一个更糟糕的解决方案。
你不应该阻塞 ThreadPool
线程,这是导致 ThreadPool
饥饿的快速方法,而是提供了一种异步等待 WaitHandle
实例的方法,这是称为 ThreadPool.RegisterWaitForSingleObject
.
通过使用 ThreadPool.RegisterWaitForSingleObject
注册一个回调,以便在 WaitHandle
可用时调用,不幸的是,这不是开箱即用的 async/await 兼容,一个完整的实现使得这个async/await兼容如下:
public static class WaitHandleExtensions
{
public static Task WaitOneAsync(this WaitHandle waitHandle, CancellationToken cancellationToken)
{
return WaitOneAsync(waitHandle, Timeout.Infinite, cancellationToken);
}
public static async Task<bool> WaitOneAsync(this WaitHandle waitHandle, int timeout, CancellationToken cancellationToken)
{
// A Mutex can't use RegisterWaitForSingleObject as a Mutex requires the wait and release to be on the same thread
// but RegisterWaitForSingleObject acquires the Mutex on a ThreadPool thread.
if (waitHandle is Mutex)
throw new ArgumentException(StringResources.MutexMayNotBeUsedWithWaitOneAsyncAsThreadIdentityIsEnforced, nameof(waitHandle));
cancellationToken.ThrowIfCancellationRequested();
var tcs = new TaskCompletionSource<bool>();
var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle, OnWaitOrTimerCallback, tcs, timeout, true);
var cancellationCallback = BuildCancellationCallback(rwh, tcs);
using (cancellationToken.Register(cancellationCallback))
{
try
{
return await tcs.Task.ConfigureAwait(false);
}
finally
{
rwh.Unregister(null);
}
}
}
private static Action BuildCancellationCallback(RegisteredWaitHandle rwh, TaskCompletionSource<bool> tcs)
{
return () =>
{
if (rwh.Unregister(null))
{
tcs.SetCanceled();
}
};
}
private static void OnWaitOrTimerCallback(object state, bool timedOut)
{
var taskCompletionSource = (TaskCompletionSource<bool>)state;
taskCompletionSource.SetResult(!timedOut);
}
}
唯一的限制是它不能与 Mutex
.
一起使用
可以这样使用:
await myAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false);
要考虑的另一种方法是使用 HttpSelfHostServer
(System.Web.Http.SelfHost.dll) 并将所有线程细节留给其实现。
var config = new HttpSelfHostConfiguration("http://localhost:9999");
var tcs = new TaskCompletionSource<Uri>();
using (var server = new HttpSelfHostServer(config, new MessageHandler(tcs)))
{
await server.OpenAsync();
await tcs.Task;
await server.CloseAsync();
}
return tcs.Task.Result;
class MessageHandler : HttpMessageHandler
{
private readonly TaskCompletionSource<Uri> _task;
public MessageHandler(TaskCompletionSource<Uri> task)
{
_task = task;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
_task.SetResult(request.RequestUri);
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
}
}
本质上,我正在做的是创建一个网络服务器来处理 API 调用,然后在完成后继续执行方法,所以本质上:
new WebServer(myAutoResetEvent);
myAutoResetEvent.WaitOne();
但是,这会阻塞线程直到那时。有没有办法使这个异步?将它包装在 await Task.Run()
调用中是否可以,即 await Task.Run(() => myAutoResetEvent.WaitOne())
?
谢谢!
通常,WebServer
ctor 不应该做任何有趣的事情。应该有一个运行服务器的 Task WebServer.RunAsync
函数。然后,您可以使用生成的任务进行同步和协调。
如果您不想这样,您可以将 TaskCompletionSource<object>
用作 one-shot async-ready 事件。
我相信 ThreadPool
class 有一种有效等待 WaitHandle
设置的方法,但这是一个更糟糕的解决方案。
你不应该阻塞 ThreadPool
线程,这是导致 ThreadPool
饥饿的快速方法,而是提供了一种异步等待 WaitHandle
实例的方法,这是称为 ThreadPool.RegisterWaitForSingleObject
.
通过使用 ThreadPool.RegisterWaitForSingleObject
注册一个回调,以便在 WaitHandle
可用时调用,不幸的是,这不是开箱即用的 async/await 兼容,一个完整的实现使得这个async/await兼容如下:
public static class WaitHandleExtensions
{
public static Task WaitOneAsync(this WaitHandle waitHandle, CancellationToken cancellationToken)
{
return WaitOneAsync(waitHandle, Timeout.Infinite, cancellationToken);
}
public static async Task<bool> WaitOneAsync(this WaitHandle waitHandle, int timeout, CancellationToken cancellationToken)
{
// A Mutex can't use RegisterWaitForSingleObject as a Mutex requires the wait and release to be on the same thread
// but RegisterWaitForSingleObject acquires the Mutex on a ThreadPool thread.
if (waitHandle is Mutex)
throw new ArgumentException(StringResources.MutexMayNotBeUsedWithWaitOneAsyncAsThreadIdentityIsEnforced, nameof(waitHandle));
cancellationToken.ThrowIfCancellationRequested();
var tcs = new TaskCompletionSource<bool>();
var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle, OnWaitOrTimerCallback, tcs, timeout, true);
var cancellationCallback = BuildCancellationCallback(rwh, tcs);
using (cancellationToken.Register(cancellationCallback))
{
try
{
return await tcs.Task.ConfigureAwait(false);
}
finally
{
rwh.Unregister(null);
}
}
}
private static Action BuildCancellationCallback(RegisteredWaitHandle rwh, TaskCompletionSource<bool> tcs)
{
return () =>
{
if (rwh.Unregister(null))
{
tcs.SetCanceled();
}
};
}
private static void OnWaitOrTimerCallback(object state, bool timedOut)
{
var taskCompletionSource = (TaskCompletionSource<bool>)state;
taskCompletionSource.SetResult(!timedOut);
}
}
唯一的限制是它不能与 Mutex
.
可以这样使用:
await myAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false);
要考虑的另一种方法是使用 HttpSelfHostServer
(System.Web.Http.SelfHost.dll) 并将所有线程细节留给其实现。
var config = new HttpSelfHostConfiguration("http://localhost:9999");
var tcs = new TaskCompletionSource<Uri>();
using (var server = new HttpSelfHostServer(config, new MessageHandler(tcs)))
{
await server.OpenAsync();
await tcs.Task;
await server.CloseAsync();
}
return tcs.Task.Result;
class MessageHandler : HttpMessageHandler
{
private readonly TaskCompletionSource<Uri> _task;
public MessageHandler(TaskCompletionSource<Uri> task)
{
_task = task;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
_task.SetResult(request.RequestUri);
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
}
}