【发布时间】:2021-10-14 13:06:19
【问题描述】:
我有一个用于网络 I/O 的 C++ 库。它使用 boost::asio 处理 TCP/IP 堆栈并使用 boost::spsc_queue 处理数据管道。它当然相当复杂,但它带有一个非常易于使用的 C++ API,可以将所有复杂性隐藏在底层。然而,这是有代价的。我目前花费时间的代价是处理库中异步 I/O 的底层函数在 API 中被公开为阻塞函数(带有超时)。也就是说,它们不再是异步的了。
C++ API 也有一个 C# 包装器。我想做的是在 C# 包装器中使用异步包装器方法公开底层库的固有异步性。但我不知道如何/是否可以为此准备我的 C++ API,我不知道如何/是否可以在我的 C# 包装器中装饰互操作样板,以便这些方法可以使用 async/await 模式。下面是一个完整的工作示例,其中使用 C# 模拟了消费者对象(为了简单起见,不是异步的)。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ASIOSimulation
{
// pretend this is a C++ library based on Boost libraries:
public class ConsumerBasedOnAsioAndSpscQueue
{
private bool _hasNewData = false;
private bool _running = false;
private Thread _thread;
public bool HasNewData => _hasNewData;
// pretend this is actually an asynchronous data consuming process:
private void Consume()
{
while (_running)
{
_hasNewData = false;
Thread.Sleep(500);
_hasNewData = true;
Thread.Sleep(500);
}
}
public void LaunchConsumer()
{
_running = true;
_thread = new Thread(Consume);
_thread.Start();
}
public void KillConsumer()
{
_running = false;
if (_thread != null)
{
if (_thread.IsAlive)
_thread.Join();
}
}
}
// pretend this is the C++ API for the library above
public class APICpp : IDisposable
{
private readonly ConsumerBasedOnAsioAndSpscQueue _consumerBasedOnAsioAndSpscQueue = new();
public APICpp()
{
_consumerBasedOnAsioAndSpscQueue.LaunchConsumer();
}
// the API for the background process gives me a blocking call
// to fetch the 'data' with
public int BlockingCallToConsumer()
{
while (!_consumerBasedOnAsioAndSpscQueue.HasNewData)
{
Thread.Sleep(10);
}
// new data has arrived! return the 'data':
return 1;
}
// how do I/can I make a nonblocking version of the above that is awaitable?
public void Dispose() => _consumerBasedOnAsioAndSpscQueue.KillConsumer();
}
// C# wrapper to C++ API---I will do my best to be async :/.
public class APIWrapperCSharp
{
private readonly APICpp _apiCpp = new();
// in reality we need something like:
// [DllImport(APICpp, CallingConventions = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
// public static extern int BlockingCallToConsumer();
public int BlockingCallToConsumer() => _apiCpp.BlockingCallToConsumer();
// what I want to implement:
public Action<int> SomeFunctionToRunAfterCallReturns { get; set; }
//public async Task NonBlockingCallAsync()
//{
// // what I don't know how to do:
// int resutl = await _apiCpp.NonBlockingCallToConsumer();
// SomeFunctionToRunAfterCallReturns!.Invoke(result);
//}
// the 'wrong' way to do it
public async Task BlockingCallAsync()
{
int result = await Task.Run(()=>_apiCpp.BlockingCallToConsumer());
SomeFunctionToRunAfterCallReturns!.Invoke(result);
}
}
// a client
public class Client
{
public void CallIt()
{
APIWrapperCSharp client = new();
int result = client.BlockingCallToConsumer();
Console.WriteLine("A non-async call to a call that blocks on an asynchronous process just got new data. Result: {0}", result);
}
public async Task CallItAsync()
{
APIWrapperCSharp client = new();
client.SomeFunctionToRunAfterCallReturns = PrintResult;
await client.BlockingCallAsync();
}
private void PrintResult(int result)
{
Console.WriteLine("An async call to a call that blocks on an asynchronous process just got new data. Result: {0}", result);
}
}
class Program
{
static void Main()
{
Client client = new();
Console.WriteLine("wait for new data");
client.CallIt();
Console.WriteLine("Control returned to Main");
Console.WriteLine("wait for new data");
client.CallItAsync();
Console.WriteLine("Control returned to Main");
}
}
}
输出如预期:
wait for new data
A non-async call to a call that blocks on an asynchronous process just got new data. Result: 1
Control returned to Main
wait for new data
Control returned to Main
An async call to a call that blocks on an asynchronous process just got new data. Result: 1
我想有两个问题。第一个是我可以在 C# 包装器中公开一个实际上是异步的 C/C++ 函数吗?如果可以,我该怎么做?第二个更理论化。我如何/我可以从头开始编写一个异步的方法——即不是仅仅调用已经异步的 .NET 进程,而是实际上是自下而上的异步?不等待任何底层的、预先存在的异步方法?
如果我理解正确,如果后台任务是同步的,则意味着它在后台线程上运行,并在安全的情况下提供对共享对象的访问。另一方面,如果它是异步的,则意味着在某些时候必须有某种回调或事件处理机制,当共享对象准备就绪时,它会为对象带来生命。那么我该如何创建这样一种机制,我可以使用方便的 async/await 模式向客户端公开呢?
【问题讨论】:
-
C# 包装器是否支持 IAsyncResult ?可以吗?
-
@HenkHolterman 不。实际上,直到现在我才知道那是什么。我需要一些时间来理解它是否可以......但我不明白为什么不。
-
当您拥有 IAsyncResult API 时,您可以查看 Task.FromAsync()。这就是 .net 中大多数异步 I/O 的实现方式。
-
WinRT 使用
IAsyncOperation和IAsyncAction。 -
要使
IAsyncResult工作,C++ 代码需要实际传递请求并返回,而不是阻塞。所以它需要某种机制来Begin一个请求和End它带有一个回调
标签: c# c++ async-await boost-asio