3

I want to wrap 3rd party function to Task to be able to await for finish the callback function. This is what I would like to achieve:

public MyClass MyProperty { 
    get
    {
        if (myProperty == null)
            myProperty = LoadMyValue("1234");
        return myProperty ;
    }
    set
    {
        myProperty = value;
    }
}

public MyClass LoadMyValue(string id) {
        return MyClassTools.LoadMyValue(id);
}

and in MyClassTools

static public MyClass LoadMyValue(string id) {
    3rdPartyApi.Call(id, Callback);
    // here I want to return Callback result
}

static MyClass Callback(3rdPartyResult result) {
    return new MyClass(result); 
}

How to use here Tasks and async/await to be able return Callback result directly from function LoadMyValue?

peterh
  • 11,875
  • 18
  • 85
  • 108
bendi
  • 133
  • 1
  • 6

1 Answers1

5

As Noseratio suggested, you can use TaskCompletionSource<T> to wrap the asynchronous API:

public static Task<3rdPartyResult> CallAsync(string id)
{
  var tcs = new TaskCompletionSource<3rdPartyResult>();
  3rdPartyApi.Call(id, result =>
  {
    if (result.Exception == null) // or whatever
      tcs.TrySetResult(result);
    else
      tcs.TrySetException(result.Exception);
  });
  return tcs.Task;
}

You can then make LoadMyValue an asynchronous method:

static public async Task<MyClass> LoadMyValueAsync(string id)
{
  return new MyClass(await CallAsync(id));
}

However, this will not help you return the value from your property. Properties cannot be asynchronous. In your case, it looks like you want an "asynchronous lazy" type of value, which you can do like this:

private readonly Lazy<Task<MyClass>> myProperty =
    new Lazy<Task<MyClass>>(() => LoadMyValueAsync("1234");
public Lazy<Task<MyClass>> MyProperty
{
  get { return myProperty; }
}

Note that I removed the setter since the semantics don't make sense for an asynchronously lazy-evaluated property.

You can then use the property like this:

MyClass value = await MyProperty.Value;

There are some types out there that can make the code slightly easier. E.g., Noseratio's type for callback wrapping, or Stephen Toub's AsyncLazy<T> (which is also a part of my AsyncEx library).

Community
  • 1
  • 1
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • +1 for `Lazy>`, I missed that part of the question (for clarity, I just used [this](http://stackoverflow.com/a/22798789/1768303) pattern). – noseratio Apr 11 '14 at 10:45
  • Thank you Stephen and Noseratio, I think I'm getting closer, but now I have this Exception: Error CS4001: Cannot await 'System.Threading.Tasks.Task<3rdPartyResult>' expression. It's on this line 'return new MyClass(await CallAsync(id));' – bendi Apr 11 '14 at 10:48
  • 1
    @bendi: If this is in a PCL, Windows Phone 7/7.5, or Silverlight project, you'll need to install the `Microsoft.Bcl.Async` NuGet package. – Stephen Cleary Apr 11 '14 at 11:17
  • Ohh:(, unfortunatelly I'm using Mono framework, which have System.Threading.Tasks, but it's not the same as the .net version:(. But thank you for your help, I'll use this in future in .net, but this time I have to choose the classic callback option, 'cause I need the program to run also on iOS, Adnroid, Windows Phone and other. – bendi Apr 11 '14 at 13:10
  • This should work just fine on Mono. Ensure all your Xamarin stuff is up-to-date, and install `Microsoft.Bcl.Async` if this is a PCL. – Stephen Cleary Apr 11 '14 at 13:19
  • Are you sure it will work on iOS or other non Windows? I'm developing now in Unity with Mono 3.3.0 framework and I don't want to do something what will be useless on other devices. I also don't use Xamarin, only pure Mono – bendi Apr 11 '14 at 13:37
  • Yes; I have run my `AsyncLazy` unit tests on MonoTouch and MonoAndroid. – Stephen Cleary Apr 11 '14 at 13:44