4

我主要使用 .NET 进行编程,我喜欢它的异步/并发原语,例如 Tasks、ResetEvents 等。今天,我第一次对 C++ 程序进行了有意义的更改,并了解了整个构建过程是如何工作的(我更新了 LigthningDB.NET 项目至 0.9.14)。但我仍然缺乏 C++ 知识。

我想添加到 LMDB 项目中的一个新功能(出于我自己的需要)是通知系统(类似于 Redis):

  • 我想要一个游标返回一个等待对象,该对象将在其表中的每个数据更改时发出信号。
  • 我想将一些数据与信号(指向数据结构的指针)一起获取,例如键或键+值。
  • 该对象将与游标一起放置,并且是游标的一部分,但它会在游标具有引用的 DB 中发出更改信号。
  • 这必须有一天(或某年)跨平台工作。如果他们完成这项工作,我可以接受任何肮脏的 Windows 特定的黑客攻击。

典型的用例是一个作家和 N 个读者在等待新消息。这将允许 IPC 和快速持久性二合一。LMDB 支持从不同进程并发读取(而 Esent 和 LevelDB 不支持)。

System.Threading原语可从 C++ 获得,我了解如何使用 WaitHandle 进行阻塞调用。有没有办法使这个异步?有很多关于异步同步原语的文章,但它们使用TaskCompletionSource并且仅在 .NET 内部有效。是否可以为本机互操作制定类似的解决方案?

一种解决方案可能是命名管道或套接字将更改转换为一个侦听器/调度程序(Redis 或Rhino.Queues样式),但性能会受到影响:写入必须分配、复制和推送数据,并且数据必须传输——比传递更糟糕指向已经在内存中的数据结构的指针。

另一种选择是将聆听光标移动到键并发送信号。发出信号后,C# 侦听器将知道光标在更新的键处具有值。这种方法解决了数据传输部分,但是使用 WaitHandles 它是阻塞的——在我的用例中阻塞比套接字 [分配/复制/延迟] 组合更糟糕。

有更好的选择吗?

附加问题:

  1. 我在这里重新发明自行车吗?
  2. 您能否指出.NET 程序等待(非阻塞)C/C++ 信号(如果存在)的开源库?
  3. 我应该为该工作流程使用 LMDB 吗?Windows 是我的优先事项,我在尝试让 LevelDB 工作时遇到了非常糟糕的时间(停止尝试)。对于 LMDB 所做的一切 + 信令,是否有更好的选择?

更新

在文档中找到了这个方法:

public static Task WaitOneAsync(this WaitHandle waitHandle)
{
    if (waitHandle == null) throw new ArgumentNullException("waitHandle");

    var tcs = new TaskCompletionSource<bool>();
    var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle, 
        delegate { tcs.TrySetResult(true); }, null, -1, true);
    var t = tcs.Task;
    t.ContinueWith(_ => rwh.Unregister(null));
    return t;
}

文档ThreadPool.RegisterWaitForSingleObject说:

等待操作由线程池中的一个线程执行。当对象的状态变为信号或超时间隔过去时,工作线程将执行委托。...等待线程使用 Win32 WaitForMultipleObjects函数来监视已注册的等待操作。

我是否正确,这是两个不同的线程,如果没有信号,第一个等待线程是唯一会阻塞的线程?

4

2 回答 2

3

RWFSO 使用特殊的线程池线程来等待多个句柄。每个线程有 63 个句柄的内置限制,因此它不如 IOCP 高效。我不会推荐基于句柄的解决方案,即使 MRE可以用来解决这个问题(MRE 可以用来解决几乎所有问题......)。

C++ 没有“事件”的概念。传统的方法是采用回调函数指针(与现在结合起来Boost.Bind并且Boost.Function几乎没有那么痛苦)。更现代的方法是Boost.Signals2.

于 2014-12-25T23:59:39.687 回答
0

更新:有一个逻辑缺陷:要使用 C 回调,必须有一个 C 进程和多个 C# 进程。但是对于 MMF,只有内存是共享的 B/W 2 个不同的 C 进程。所以我要么需要像 Redis 这样的 C 服务器(但我们已经有了 Redis!),要么需要进程间信令。

我有可靠同步和 Redis的经验,这就是问题的根源——我想要一个类似的解决方案,但对于具有持久性的本地机器。

可能用命名的 MRE/ARE 重写 Redis 逻辑是最简单的方法。正如斯蒂芬所说,MRE 可以做任何事情。


(任何投票都应该由 Stephen Cleary 回答,这里只是一个例子)

答案是使用 TaskCompletionSource 和 Callback 来设置其结果。

回调示例:

C#:

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
delegate int CompareCallback(int a, int b);

[SuppressUnmanagedCodeSecurity]
class NativeMethod
{

    [DllImport("CppDynamicLinkLibrary.dll", CharSet = CharSet.Auto)]
    public static extern int CompareInts(int a, int b, CompareCallback cmpFunc);

}

C:

// Type-definition: 'PFN_COMPARE' now can be used as type
typedef int (CALLBACK *PFN_COMPARE)(int, int);

// An exported/imported stdcall function using a DEF file
// It requires a callback function as one of the arguments
int __stdcall CompareInts(int a, int b, PFN_COMPARE cmpFunc)
{
    // Make the callback to the comparison function

    // If a is greater than b, return a; 
    // If b is greater than or equal to a, return b.
    return ((*cmpFunc)(a, b) > 0) ? a : b;
}

示例代码:https ://code.msdn.microsoft.com/windowsdesktop/CSPInvokeDll-b05779d0

于 2014-12-27T11:33:35.480 回答