将ManualResetEvent包装为等待任务

nos*_*tio 29 .net c# multithreading task-parallel-library async-await

我想等待手动重置事件,超时并观察取消.我想出了类似下面的东西.手动重置事件对象由我无法控制的API提供.有没有办法在不接受和阻止ThreadPool线程的情况下实现这一点?

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 
}
Run Code Online (Sandbox Code Playgroud)

将帖子显然,这是有道理的使用RegisterWaitForSingleObject.我试试看.

Ste*_*ary 45

RegisterWaitForSingleObject将等待组合到专用的服务器线程上,每个线程都可以在多个句柄上等待(具体来说,其中有63个,MAXIMUM_WAIT_OBJECTS对于"控制"句柄是减1).

所以你应该可以使用这样的东西(警告:未经测试):

public static class WaitHandleExtensions
{
    public static Task AsTask(this WaitHandle handle)
    {
        return AsTask(handle, Timeout.InfiniteTimeSpan);
    }

    public static Task AsTask(this WaitHandle handle, TimeSpan timeout)
    {
        var tcs = new TaskCompletionSource<object>();
        var registration = ThreadPool.RegisterWaitForSingleObject(handle, (state, timedOut) =>
        {
            var localTcs = (TaskCompletionSource<object>)state;
            if (timedOut)
                localTcs.TrySetCanceled();
            else
                localTcs.TrySetResult(null);
        }, tcs, timeout, executeOnlyOnce: true);
        tcs.Task.ContinueWith((_, state) => ((RegisteredWaitHandle)state).Unregister(null), registration, TaskScheduler.Default);
        return tcs.Task;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我正在使用`RegisterWaitForSingleObject`和`ContinueWith`上的状态参数作为优化.这比"WaitAny"更受欢迎,因为这种等待可以与其他等待相结合. (2认同)
  • @JotaBe:这些天我有一种[更现代的方法](https://github.com/StephenCleary/AsyncEx/blob/8a73d0467d40ca41f9f9cf827c7a35702243abb8/src/Nito.AsyncEx.Interop.WaitHandles/Interop/WaitHandleAsyncFactory.cs)。两种方法都假设句柄最终会收到信号(或超时)。如果不是,则任务将无法完成。因此,如果您有一个循环或在“*Any”方法中使用它的东西,您的代码可能每次都会启动一个*新的*等待,而不是使用现有的等待。 (2认同)

Mil*_*les 11

您还可以使用与ManualResetEvent类似的SemaphoreSlim.WaitAsync()

  • 问题是没有"等待没有减少".有时你只是想等待而不是递减计数器. (6认同)
  • `SemaphoreSlim` 类似于 `AutoResetEvent`,而不是 `ManualResetEvent` (3认同)

Yev*_*kes 5

斯蒂芬的克利里解决方案看起来很完美。Microsoft 提供了类似的一种

因为我还没有看到带有取消逻辑的示例。

这里是:

public static class WaitHandleExtensions
{
    public static Task WaitOneAsync(this WaitHandle waitHandle, CancellationToken cancellationToken, int timeoutMilliseconds = Timeout.Infinite)
    {
        if (waitHandle == null)
            throw new ArgumentNullException(nameof(waitHandle));

        TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
        CancellationTokenRegistration ctr = cancellationToken.Register(() => tcs.TrySetCanceled());
        TimeSpan timeout = timeoutMilliseconds > Timeout.Infinite ? TimeSpan.FromMilliseconds(timeoutMilliseconds) : Timeout.InfiniteTimeSpan;

        RegisteredWaitHandle rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle,
            (_, timedOut) =>
            {
                if (timedOut)
                {
                    tcs.TrySetCanceled();
                }
                else
                {
                    tcs.TrySetResult(true);
                }
            }, 
            null, timeout, true);

        Task<bool> task = tcs.Task;

        _ = task.ContinueWith(_ =>
        {
            rwh.Unregister(null);
            return ctr.Unregister();
        }, CancellationToken.None);

        return task;
    }
}
Run Code Online (Sandbox Code Playgroud)