如何在DropNet中实现CancellationToken支持?

时间:2012-11-12 15:52:21

标签: c# task-parallel-library restsharp cancellation dropnet

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

两个库都运行良好,但返回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实施基于TaskCompletionSourcewritten by Laurent Kempé原{{3}}:

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
);

1 个答案:

答案 0 :(得分:1)

因为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的{​​{1}}以及HttpWebRequest方法。这就是我们取消事情的方式。

Abort

最后,Lauren's implementation将请求置于线程池中,但我没有理由这样做 - 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; } 本身是异步的。所以我不这样做。

这就是取消DropNet操作的原因。

我也做了一些调整,可能对你有用。

因为我无法调度DropBox ExecuteAsync而无需将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支持的“真实”任务中。