Using async and waiting when the external I / O API provides its own callback delegates

I have a class library that (among other things) acts as a wrapper for an external client library for a web API.

My (simplified) code here takes a query string, generates an object ReportUtilities

, uses that to load the report, then returns the report string back to the caller:

public string GetXmlReport(string queryString)
{
    ReportUtilities reportUtil = new ReportUtilities(_user,queryString,"XML");

    byte[] data = reportUtil.GetResponse().Download();
    return Encoding.UTF8.GetString(data);
}

      

The problem is that this method is loading data from the web service using a synchronous method. I would like to make this available asynchronously in my library by adding a method GetXmlReportAsync()

to my class.

Now if the class ReportUtilities

provided a method GenerateAndDownloadAsync()

that returns Task<byte[]>

, but unfortunately it is not and it is in an external library, so I am stuck with what it provides.

The class ReportUtilities

has a method GetResponseAsync()

that returns void and provides a delegate for method a OnReadyCallback

along with a property OnReadyCallback OnReady{get;set;}

.

I must add that it .GetResponse()

returns an object ReportResponse

that has a method DownloadAsync()

, but again returns void instead Task<byte[]>

. ReportResponse

Comes again with a OnDownloadSuccessCallback

delegate and a OnDownloadSuccessCallback OnDownloadSuccess { get; set; }

property.

Is it almost as if the authors of the external library were "wrapping their own" asynchronous API rather than using the built-in one in C #?

My question is, how can I implement a method GetXmlReportAsync()

in my class to make the most efficient use of the asynchronous functions in the client library?

Obviously, I could just do:

public async Task<string> GetXmlReportAsync(string queryString)
{
    ReportUtilities reportUtil = new ReportUtilities(_user,queryString,"XML");

    byte[] data = await Task.Run(() => { return reportUtil.GetResponse().Download(); });
    return Encoding.UTF8.GetString(data);
}

      

but then the stream is associated with 2 synchronous I / O calls to an external library: .GetResponse()

and .Download()

, which are definitely not optimal?

Alternatively, I could imagine a situation where I was just showing a similar API to an external library in my own client, where clients had to provide callbacks when their reports are ready, but I would rather include them in the more familiar asynchronous / await style API.

Am I trying to set a square snap to a round hole or am I missing a neat way to wrap this in an async / await API?

+3


source to share


2 answers


Is it almost as if the authors of the external library were "wrapping their own" asynchronous API rather than using the built-in one in C #?

Non-standard situation for older libraries, especially those that have been directly ported from other platforms / languages.

Am I trying to set a square snap to a round hole or am I missing a neat way to wrap this in an async / await API?

The pattern they use is very similar to EAP , and there is a common pattern for converting EAP to TAP . You can do something like this with a few settings.

I recommend creating extension methods for third party library types that give you nice TAP endpoints and then build your logic on top of that. So the TAP method does not mix problems (translating asynchronous templates and doing business logic, i.e. Converting to string).

The ReportUtilities class has a GetResponseAsync () method that returns void and provides a delegate for the OnReadyCallback method along with an OnReadyCallback property OnReady {get; set;}.



Something like this, then:

public static Task<ReportResponse> GetResponseTaskAsync(this ReportUtilities @this)
{
  var tcs = new TaskCompletionSource<ReportResponse>();
  @this.OnReady = response =>
  {
    // TODO: check for errors, and call tcs.TrySetException if one is found.
    tcs.TrySetResult(response);
  };
  @this.GetResponseAsync();
  return tcs.Task;
}

      

Likewise for the next level:

public static Task<byte[]> DownloadTaskAsync(this ReportResponse @this)
{
  var tcs = new TaskCompletionSource<byte[]>();
  // TODO: how to get errors? Is there an OnDownloadFailed?
  @this.OnDownloadSuccess = result =>
  {
    tcs.TrySetResult(result);
  };
  @this.DownloadAsync();
  return tcs.Task;
}

      

Then your business logic can use pure TAP endpoints:

public async Task<string> GetXmlReportAsync(string queryString)
{
  ReportUtilities reportUtil = new ReportUtilities(_user, queryString, "XML");

  var response = await reportUtil.GetResponseTaskAsync().ConfigureAwait(false);
  var data = await response.DownloadTaskAsync().ConfigureAwait(false);
  return Encoding.UTF8.GetString(data);
}

      

+2


source


How can I implement the GetXmlReportAsync () method in my class to make the most efficient use of the asynchronous functions in the client library?

You can wrap an async call GetResponseAsync

with TaskCompletionSource<string>

. It will register the delegate after completion and set the completion of the task with SetResult

. It would look something like this:



public Task<string> GetXmlReportAsync()
{
    var tcs = new TaskCompletionSource<string>();
    ReportUtilities reportUtil = new ReportUtilities(_user,queryString,"XML");

    reportUtil.GetResponseAsync(callBack => 
    {
        // I persume this would be the callback invoked once the download is done
        // Note, I am assuming sort sort of "Result" property provided by the callback,
        // Change this to the actual property
        byte[] data = callBack.Result;
        tcs.SetResult(Encoding.UTF8.GetString(data));
    });

    return tcs.Task;
}

      

+3


source







All Articles