Как сделать неблокирующую ручку ожидания?

По сути, я создаю веб-сервер для обработки вызова 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));
    }
}
Другие вопросы по тегам