Joey Herrington
Joey Herrington

Reputation: 179

Wrapping Native Async functions with Task<T>

I'm attempting to write a c# wrapper around a third-party library written in native code for consumption in our apps, which are almost exclusively written in .NET, and I'm trying to remain faithful to the C# patterns. Almost all the calls in this library are asynchronous in nature, and it would seem appropriate to wrap all my async calls into Task<T> objects. Here's an oversimplified example of how the native library is structured:

delegate void MyCallback(string outputData);

class MyNativeLibrary
{
    public int RegisterCallback(MyCallback callback); // returns -1 on error
    public int RequestData(string inputData);         // returns -1 on error
}

Right now, I've provided my return values through event subscription, however I believe this would be a far better way to return my data:

class WrapperAroundNativeCode
{
    public async Task<string> RequestData(string inputData);
}

So far I've been unsuccessful in finding an appropriate way to implement this, and I'm reaching out to folks with more experience in working with Task<T> objects and the async/await pattern than I do.

Upvotes: 5

Views: 2658

Answers (2)

Daniel Hilgarth
Daniel Hilgarth

Reputation: 174369

You would use a TaskCompletionSource<TResult> for this. Something along the lines of the following code:

class WrapperAroundNativeCode
{
    public async Task<string> RequestData(string inputData)
    {
        var completionSource = new TaskCompletionSource<string>();
        var result = Native.RegisterCallback(s => completionSource.SetResult(s));
        if(result == -1)
        {
            completionSource.SetException(new SomeException("Failed to set callback"));
            return completionSource.Task;
        }

        result = Native.RequestData(inputData);
        if(result == -1)
            completionSource.SetException(new SomeException("Failed to request data"));
        return completionSource.Task;
    }
}

This answer assumes that there won't be concurrent calls to this method. If there were you would need some way to differentiate between the different calls. Many APIs provide a userData payload that you can set to a unique value per call, so that you can differentiate.

Upvotes: 12

Jon Skeet
Jon Skeet

Reputation: 1502106

It sounds like you're looking for TaskCompletionSource<T>. You'd wrap your library by creating a TaskCompletionSource, creating an instance of MyNativeLibrary and registering a callback which set the result of the task completion source, and then requesting data from same instance. If either of these steps fails, set an error on the task completion source. Then just return the value of the TaskCompletionSource<>.Task property to the caller.

(This is assuming you can create separate instances of MyNativeLibrary - if you can only create a single instance across your whole app, it gets a lot harder.)

Upvotes: 10

Related Questions