我想在MonoTouch应用程序中异步访问DropBox API。
我认为使用本身依赖于DropNet的RestSharp会很方便。
这两个库都可以正常工作,但返回Task
的DropNet重载无法为您提供将请求与取消令牌相关联的方法。
这是其实现的外观:
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
实现基于最初was的TaskCompletionSource
和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;
}
}
如何更改或扩展此代码以支持使用
CancellationToken
取消?我想这样称呼它:
var task = dropbox.GetThumbnailTask(
"/test.jpg", ThumbnailSize.ExtraLarge2, _token
);
最佳答案
由于CancellationToken
是值类型,因此我们可以将其作为可选参数添加到具有默认值的API中,并避免进行空检查,这很不错。
public Task<IRestResponse> GetThumbnailTask(
string path, ThumbnailSize size, CancellationToken cancel = default(CancellationToken)
) {
if (!path.StartsWith("/")) path = "/" + path;
var request = _requestHelper.CreateThumbnailRequest(path, size, Root);
return ExecuteTask(ApiType.Content, request, cancel);
}
现在,RestSharp
ExecuteAsync
方法将返回封装了基础RestRequestAsyncHandle
的HttpWebRequest
和Abort
方法。这就是我们取消事情的方式。public static Task<TResult> ExecuteTask<TResult>(
this IRestClient client, IRestRequest request, CancellationToken cancel = default(CancellationToken)
) where TResult : new()
{
var tcs = new TaskCompletionSource<TResult>();
try {
var async = client.ExecuteAsync<TResult>(request, (response, _) => {
if (cancel.IsCancellationRequested || response == null)
return;
if (response.StatusCode != HttpStatusCode.OK) {
tcs.TrySetException(new DropboxException(response));
} else {
tcs.TrySetResult(response.Data);
}
});
cancel.Register(() => {
async.Abort();
tcs.TrySetCanceled();
});
} catch (Exception ex) {
tcs.TrySetException(ex);
}
return tcs.Task;
}
public static Task<IRestResponse> ExecuteTask(this IRestClient client, IRestRequest request, CancellationToken cancel = default(CancellationToken))
{
var tcs = new TaskCompletionSource<IRestResponse>();
try {
var async = client.ExecuteAsync<IRestResponse>(request, (response, _) => {
if (cancel.IsCancellationRequested || response == null)
return;
if (response.StatusCode != HttpStatusCode.OK) {
tcs.TrySetException(new DropboxException(response));
} else {
tcs.TrySetResult(response);
}
});
cancel.Register(() => {
async.Abort();
tcs.TrySetCanceled();
});
} catch (Exception ex) {
tcs.TrySetException(ex);
}
return tcs.Task;
}
最后,Lauren's implementation将请求放入线程池,但我看不出这样做的原因-
ExecuteAsync
本身是异步的。所以我不这样做。这就是取消DropNet操作的方法。
我还做了一些调整,可能对您有用。
因为我无法安排DropBox
Task
而不用将ExecuteTask
调用包装到另一个Task
中,所以我决定为请求找到一个最佳的并发级别,结果对我来说是4
,并且明确设置:static readonly Uri DropboxContentHost = new Uri("https://api-content.dropbox.com");
static DropboxService()
{
var point = ServicePointManager.FindServicePoint(DropboxContentHost);
point.ConnectionLimit = 4;
}
我也很满意让未处理的任务异常在地狱中腐烂,所以我就是这样做的:
TaskScheduler.UnobservedTaskException += (sender, e) => {
e.SetObserved();
};
最后一个观察结果是您不应在
Start()
返回的任务上调用DropNet
,因为该任务立即启动。如果您不喜欢这样做,则需要将ExecuteTask
包装在另一个不受TaskCompletionSource
支持的“真实”任务中。