If you’ve been using Xamarin for a while you probably already know of this functionality.
Just in case you haven’t I just want to give shout out to the TaskCompletionSource class. In short the completion source allows you to create a Task and return it to the caller without having to set the result or use an lambda.
It’s great for working with event based API’s like Native Bluetooth, NSSession and Native mapping engines. All of which provide results via events. The Native Bluetooth API in iOS is a great example of this, so let’s take a look.
TaskCompletionSource and Native API Sample
The function we want to build is below, it returns an awaitable task of float which is the Bluetooth Signal strength.
/// <summary> /// Gets the Signal strength of Bluetooth Device /// </summary> /// <returns>The signal strength.</returns> public Task<float> GetSignalStrength ()
public Task<float> GetSignalStrength () { var bluetoothCompletion = new TaskCompletionSource<float> (); //do bluetooth work return bluetoothCompletion.Task; }
Now that we have the TaskCompletionSource there’s a few things we can set:
bluetoothCompletion.SetCanceled (); //Set the task as Canceled bluetoothCompletion.SetResult (888); //Sets the result as successful bluetoothCompletion.SetException (new Exception ()); //Sets an exception
Great the caller has a Task that they can await. Now let’s do the Bluetooth work.
var centralManager = new CBCentralManager(DispatchQueue.CurrentQueue); centralManager.DiscoveredPeripheral += (object sender, CBDiscoveredPeripheralEventArgs e) => { bluetoothCompletion.SetResult(e.RSSI.FloatValue); }; centralManager.FailedToConnectPeripheral += (object sender, CBPeripheralErrorEventArgs e) => { bluetoothCompletion.TrySetException(new Exception("Failed to connect to device")); };
Now let’s put them all together.
/// <summary> /// Gets the Signal strength of Bluetooth Device /// </summary> /// <returns>The signal strength.</returns> public Task<float> GetSignalStrength () { var bluetoothCompletion = new TaskCompletionSource<float> (); var centralManager = new CBCentralManager(DispatchQueue.CurrentQueue); centralManager.DiscoveredPeripheral += (object sender, CBDiscoveredPeripheralEventArgs e) => { bluetoothCompletion.SetResult(e.RSSI.FloatValue); }; centralManager.FailedToConnectPeripheral += (object sender, CBPeripheralErrorEventArgs e) => { bluetoothCompletion.TrySetException(new Exception("Failed to connect to device")); }; return bluetoothCompletion.Task; }
Great, so now the caller can easily consumer this API in a async way.
var bluetoothStrength = await bluetoothService.GetSignalStrength();
This was a very simple example but the TaskCompletionSource can be used in many scenarios, it can be passed into functions and kept as member variables until you need it. So if it’s not already in your Toolkit, add it and use it, your code will be cleaner.
If you want to know more feel free to drop me a email.
Thanks
Michael
All though i am developing Xamarin from past 1.5years, i always thought TaskCompletionSource is used to make Synchronous calls. I never knew i can used await from the “caller” 🙂