dmedine
dmedine

Reputation: 1563

is it possible to make an asynchronous C/C++ function awaitable in C# without resorting to Task.Run()?

I have a C++ library that does network I/O stuff. It uses boost::asio to handle the TCP/IP stack and boost::spsc_queue to handle the data piping. It is, of course rather complicated but it comes with a pretty easy-to-use C++ API that keeps all the complexity under the hood. However, this comes at some cost. The cost that I am currently spending time on is that the underlying functions that handle the asynchronous I/O in the library are exposed as blocking functions (with timeouts) in the API. That is to say, they are no longer asynchronous.

The C++ API also has a C# wrapper. What I would like to do is expose the inherent asynchronous-ness of the underlying library with async wrapper methods in the C# wrapper. But I don't know how to/if I can prepare my C++ API for this and I don't know how to/if I can decorate the interop boilerplate in my C# wrapper so that the methods can use the async/await pattern. Below is a full working example with the consumer object simulated (not asynchronously, for simplicity's sake) in 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");
        }
    }
}

Output is as expected:

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

I guess there are two questions. The first is can I expose a C/C++ function that actually is asynchronous as awaitable in a C# wrapper and if so, how do I do that? The second is more theoretical. How do I/can I write a method that is async from scratch---i.e. not one that merely calls .NET processes that are already async, but actually is async from the very bottom up? that doesn't await any underlying, pre-existing async method?

If I understand correctly, if a background task is synchronous, it implies that it runs on a background thread and provides access to shared objects when it is safe to do so. On the other hand, if it is asynchronous, it implies that at some point there must be some kind of callback or event handler mechanism that will come bring an object to life when the shared objects are ready to go. So how do I go about creating such a mechanism that I can expose to clients with the handy-dandy async/await pattern?

Upvotes: 2

Views: 1037

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456687

The first is can I expose a C/C++ function that actually is asynchronous as awaitable in a C# wrapper and if so, how do I do that?

This is on my "to blog about" list for years now. Most folks don't need it.

The short answer is that you need some kind of callback system, as you suspected.

The common approach for the Win32 API (and closely-related APIs) is to use overlapped I/O. The .NET wrappers then use ThreadPool.BindHandle to bind the HANDLE to the I/O completion port built into the thread pool. Finally, asynchronous operations specify a callback when passing the OVERLAPPED structure to the unmanaged code (e.g., using Overlapped.Pack). This callback can complete a TaskCompletionSource<T>, avoiding the need for IAsyncResult completely.

This is the common approach for systems built into Windows, but isn't common for third-party unmanaged libraries. One reason is because writing truly asynchronous unmanaged code is quite difficult. Most unmanaged libraries don't bother with asynchronous code at all; the few that do often use bespoke callbacks or some similar custom approach. If the C++ API is using something like a simple callback, then you can marshal a delegate that completes a TaskCompletionSource<T> directly.

The second is more theoretical. How do I/can I write a method that is async from scratch---i.e. not one that merely calls .NET processes that are already async, but actually is async from the very bottom up? that doesn't await any underlying, pre-existing async method?

This can be done using TaskCompletionSource<T>. The tricky part (especially when marshaling to unmanaged code) is how to set up the callback so that the TaskCompletionSource<T> is completed.

So how do I go about creating such a mechanism that I can expose to clients with the handy-dandy async/await pattern?

There isn't a way to marshal async/await across interop boundaries (yet). The unmanaged C++ world doesn't have a standard, common Promise/Future type (i.e., Task<T> / TaskCompletionSource<T>), so libraries use whatever they come up with. Then the managed world takes whatever that solution is and (eventually) wraps it in TaskCompletionSource<T>, which produces a Task<T> which can be awaited.

Upvotes: 4

Related Questions