我想在MonoTouch应用程序中异步访问DropBox API。
我认为使用本身依赖于DropNetRestSharp会很方便。

这两个库都可以正常工作,但返回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实现基于最初wasTaskCompletionSourcewritten 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方法将返回封装了基础RestRequestAsyncHandleHttpWebRequestAbort方法。这就是我们取消事情的方式。

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支持的“真实”任务中。

09-05 13:53