Как сделать неблокирующую ручку ожидания?
По сути, я создаю веб-сервер для обработки вызова API, а затем, когда это будет сделано, продолжит выполнение метода, по сути:
new WebServer(myAutoResetEvent);
myAutoResetEvent.WaitOne();
Тем не менее, это блокирует поток до тех пор. Есть ли способ сделать этот асинхронный? Это нормально, просто обернуть его в await Task.Run()
звоните, т.е. await Task.Run(() => myAutoResetEvent.WaitOne())
?
Спасибо!
3 ответа
Обычно WebServer
ctor не должен делать ничего интересного. Там должно быть Task WebServer.RunAsync
функция, которая запускает сервер. Затем вы можете использовать полученную задачу для синхронизации и координации.
Если вы не хотите, чтобы вы могли использовать TaskCompletionSource<object>
как одноразовый асинхронный готовый случай.
Я верю ThreadPool
у класса есть способ эффективно ждать 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));
}
}