I want to asynchronously access DropBox API in a MonoTouch app.
I thought it would be convenient to use DropNet which itself relies on RestSharp.
Both libraries work well but DropNet overloads that return Tasks don’t give you a way to associate requests with cancellation tokens.
This is how their implementation looks:
public Task<IRestResponse> GetThumbnailTask(string path, ThumbnailSize size)
{
if (!path.StartsWith("/")) path = "/" + path;
var request = _requestHelper.CreateThumbnailRequest(path, size, Root);
return ExecuteTask(ApiType.Content, request, cancel);
}
ExecuteTask implementation is based on TaskCompletionSource and was originally written by Laurent Kempé:
public static class RestClientExtensions
{
public static Task<TResult> ExecuteTask<TResult>(
this IRestClient client, IRestRequest request
) where TResult : new()
{
var tcs = new TaskCompletionSource<TResult>();
WaitCallback asyncWork = _ => {
try {
client.ExecuteAsync<TResult>(request,
(response, asynchandle) => {
if (response.StatusCode != HttpStatusCode.OK) {
tcs.SetException(new DropboxException(response));
} else {
tcs.SetResult(response.Data);
}
}
);
} catch (Exception exc) {
tcs.SetException(exc);
}
};
return ExecuteTask(asyncWork, tcs);
}
public static Task<IRestResponse> ExecuteTask(
this IRestClient client, IRestRequest request
)
{
var tcs = new TaskCompletionSource<IRestResponse>();
WaitCallback asyncWork = _ => {
try {
client.ExecuteAsync(request,
(response, asynchandle) => {
if (response.StatusCode != HttpStatusCode.OK) {
tcs.SetException(new DropboxException(response));
} else {
tcs.SetResult(response);
}
}
);
} catch (Exception exc) {
tcs.SetException(exc);
}
};
return ExecuteTask(asyncWork, tcs);
}
private static Task<TResult> ExecuteTask<TResult>(
WaitCallback asyncWork, TaskCompletionSource<TResult> tcs
)
{
ThreadPool.QueueUserWorkItem(asyncWork);
return tcs.Task;
}
}
How do I change or extend this code to support cancellation with CancellationToken?
I’d like to call it like this:
var task = dropbox.GetThumbnailTask(
"/test.jpg", ThumbnailSize.ExtraLarge2, _token
);
Because
CancellationTokenis a value type, we can add it as an optional parameter to the APIs with default value and avoid null checks, which is sweet.Now, RestSharp
ExecuteAsyncmethod returnsRestRequestAsyncHandlethat encapsulates underlyingHttpWebRequest, along withAbortmethod. This is how we cancel things.Finally, Lauren’s implementation puts requests in thread pool but I don’t see a reason to do so—
ExecuteAsyncis asynchronous itself. So I don’t do that.And that’s it for cancelling DropNet operations.
I also made a few tweaks which may be useful to you.
Because I had no way of scheduling DropBox
Tasks without resorting to wrappingExecuteTaskcalls into anotherTasks, I decided to find an optimal concurrency level for requests, which turned out to be4for me, and set it explicitly:I was also content with letting unhandled task exceptions rot in hell, so that’s what I did:
One last observation is you shouldn’t call
Start()on a task returned byDropNetbecause the task starts right away. If you don’t like that, you’ll need to wrapExecuteTaskin yet another “real” task not backed byTaskCompletionSource.