首页 > 解决方案 > 如何在 C# 中以异步方式等待多个 WaitHandle 数组并支持取消?

问题描述

C# 有一个推荐的方法来以非阻塞方式等待单个 WaitHandle 对象ThreadPool.RegisterWaitForSingleObject。但我需要类似的东西,但需要多个对象。类似WaitHandle.WaitAll但在异步变体中的东西。如何以更少资源浪费的方式实现这一目标?现在我考虑创建任务并在那里等待句柄,如下所示:

public static class WaitHandleExtension
{
    public static Task<bool> WaitAllAsync ( this WaitHandle[] handles )
    {
        return Task.Factory.StartNew( () => WaitHandle.WaitAll( handles ) );
    }
}

但我无法以这种方式实现取消。在取消支持的情况下等待所有句柄异步的最佳方法是什么?

UPD:在这里我描述了一个我正在解决的任务,也许有人会建议一种不同的方式来异步实现它:

我正在实现跨平台 IPC 流,它的工作方式类似于命名管道,但以简化的方式。它应该是完全异步的并支持取消。它应该支持 macOS 和 Windows 上的 Mono,因为服务器应用程序依赖于 Mono 并且不能在 Windows 上使用 .NET。问题是:Windows 上的 Mono 根本不支持 UNIX 域套接字,并且命名管道实现不完整(在 Mono Windows 上无法取消命名管道连接)。我在 UNIX 域套接字上实现了 macOS 部分,效果很好。在 Windows 上,我决定基于内存映射文件实现我自己的 Stream 类。它有两个共享的读写缓冲区用于双工数据交换。每个缓冲区由命名信号量保护,并有两个命名事件 EventWaitHandle,一个表示缓冲区不为空,另一个表示缓冲区未满。所以,发送方法等待信号量和非满事件来用数据填充缓冲区并发出非空事件信号,如果缓冲区被满足,它会重置非满事件。读取等待信号量和非空事件来消耗数据并发出非完整事件信号,如果它消耗了所有数据,它会重置非空事件。它以同步方式完美运行。但是实现可取消的 ReadAsync 和 WriteAsync 会很棒。

标签: c#asynchronoustaskwaithandle

解决方案


这可行,但感觉有点脏。这个想法是有效地等待任何一个句柄完成,包括取消令牌的句柄。如果其中一个句柄(取消令牌除外)完成,则将其从剩余句柄列表中删除并再次有效地等待,直到所有句柄都完成或取消令牌等待句柄完成。

static async Task<bool> WaitForHandlesAsync(WaitHandle[] handles, CancellationToken cancellationToken) {
    List<WaitHandle> remainingHandles = new List<WaitHandle>(handles.Length + 1);
    remainingHandles.Add(cancellationToken.WaitHandle);
    remainingHandles.AddRange(handles);
    bool canceled = cancellationToken.IsCancellationRequested;
    while (remainingHandles.Count > 1 && !canceled) {
        int idx = await Task.Factory.StartNew(() => WaitHandle.WaitAny(remainingHandles.ToArray()));
        if (idx == 0)
            canceled = true;
        else {
            remainingHandles.RemoveAt(idx);
        }
    }
    return !canceled;
}

推荐阅读