2013-12-12 72 views
0

我正在创建一个通用加载程序,我想启动一个HttpClient SendAsync请求。但是,其中一些请求可能需要时间,所以我想添加取消选项,并在完成时通知。HttpClient异步任务管理

这看起来像是一个标准的情景imho。

我不确定这是否是正确的方式去解决这个问题,但基于我看过的一些例子,这里是我所处的位置。如果你看看代码的底部,我的问题是 - 在这一点上,我检查响应并提出成功或错误事件

public bool StartFetch() 
    { 
     if (IsFetching) return false; 
     IsFetching = true; 

     mCancellationTokenSource = new CancellationTokenSource(); 

     // this is not awaited, so execution should continue 
     StartTask(request, mCancellationTokenSource.Token); 
     return true; 
    } 

    public bool CancelFetch() 
    { 
     // send cancellation 
     if (mCancellationTokenSource != null) 
      mCancellationTokenSource.Cancel(); 

     Cleanup(); 
     return true; 
    } 


    private async Task StartTask(LFHttpRequest request, CancellationToken cancellationToken) 
    { 
     var message = new HttpRequestMessage(request.Method, request.Uri); 
     var response = await HttpClient.SendAsync(message, cancellationToken); 

     // at this point, do I take a look at response and raise a custom OnSuccess or OnError event??? 

     // or do I want to grab the task from `SendAsync`, check for completed or faulted? 
    } 
+0

为什么不直接使用'StartTask()''public''?我认为这会解决你所有的问题。 – svick

回答

0

我会建议为StartFetchCancelFetch操作抛出InvalidOperationException■如果IsFetching状态无效。这看起来很烦人,但它可以让你在程序员错误和线程问题成为更大,隐藏的问题之前发现问题。

至于你的异步方法,你的方法应该返回一个结果。所以也许就像private async Task<MyHttpResult> StartTask(...)。你的结果应该包含一个确定成功,失败和取消的方法。

例如:

public sealed class MyHttpResult 
{ 
    public HttpResponse Result { get; private set; } 
    public Exception Error { get; private set; } 
    public bool WasCancelled { get; private set; } 

    public MyHttpResult(HttpResponse result, Exception error, bool wasCancelled) 
    { 
    this.Result = result; 
    this.Error = error; 
    this.WasCancelled = wasCancelled; 
    } 
} 

许多异步方法将抛出一个TaskCanceledException,如果他们被取消,这样你就可以catch,为表示,像这样:

async Task<MyHttpResult> StartTask(LFHttpRequest request, CancellationToken cancellationToken) 
{ 
    var message = new HttpRequestMessage(new HttpMethod(request.Method), request.Uri); 

    HttpResponse response = null; 
    Exception lastError = null; 
    bool wasCancelled = false; 

    try 
    { 
    response = await MessageInvoker.SendAsync(message, cancellationToken); 
    } 
    catch(TaskCanceledException) 
    { 
    wasCancelled = true; 
    } 
    catch(Exception ex) 
    { 
    lastError = ex; 
    } 

    var result = new MyHttpResult(response, lastError, wasCancelled); 
    return result; 
} 

这是所有假设你的观察员也是来电者,所以他们可以用await这个方法。如果情况并非如此,那么您对EventHandler的想法是有道理的。相反,返回结果的,你可以创建一个自定义EventArgs类,像这样:

public delegate void TaskResultEventHandler<T>(object sender, TaskResultEventArgs<T> e); 

public sealed class TaskResultEventArgs<T> : EventArgs 
{ 
     public T Result { get; private set; } 
     public Exception Error { get; private set; } 
     public bool WasCancelled { get; private set; } 

     public TaskResultEventArgs(T result, Exception error, bool wasCancelled) 
     { 
     this.Result = result; 
     this.Error = error; 
     this.WasCancelled = wasCancelled; 
     } 
} 

然后它只是露出TaskResultEventHandler<HttpResponse>和你的观察家订阅它的问题。你可以调用它像这样:

var handler = this.HttpTaskCompleted; 

if(handler != null) 
    handler(this, new TaskResultEventArgs<HttpResponse>(response, lastError, wasCancelled)); 
+0

但我不希望它返回结果,或者调用者不关心结果。我确实有观察者关心结果,因此当任务完成时,我想知道是否应该关闭适当的成功/错误事件。想法? – earthling

+0

我假设观察员是**而不是**调用函数?否则,他们可以自己“等待”结果。 您可以创建一个类似'EventArgs'类(类似于'BackgroundWorker'中的'RunWorkerCompletedEventArgs')来代替返回结果。我会相应地更新我的答案。 – Erik

3

当你正在看的暴露与任务相关的状态一样IsFetching,它往往更清洁和更容易只露出Task本身。

事情是这样的:你期待已久的HTTP调用

var response = await HttpClient.SendAsync(message, cancellationToken); 

public Task<T> FetchTask { get; private set; } 

public bool StartFetch() 
{ 
    if (FetchTask != null) return false; 

    mCancellationTokenSource = new CancellationTokenSource(); 
    FetchTask = FetchAsync(request, mCancellationTokenSource.Token); 
    return true; 
} 

public bool CancelFetch() 
{ 
    // send cancellation 
    if (mCancellationTokenSource != null) 
     mCancellationTokenSource.Cancel(); 

    FetchTask = null; 
    return true; 
} 


private async Task<T> FetchAsync(LFHttpRequest request, CancellationToken cancellationToken) 
{ 
    var message = new HttpRequestMessage(request.Method, request.Uri); 
    var response = await HttpClient.SendAsync(message, cancellationToken); 
    response.EnsureSuccessStatusCode(); 
    var ret = // Convert response.Content into T. 
    return ret; 
} 
0

后,您应该测试取消:

if(cancellationToken.IsCancellationRequested) 
    //... do what you want, throw or return false or null, depending on how you want to handle this cancellation. 

或者你可以检查并抛出微软例外在一个电话中:

cancel.ThrowIfCancellationRequested();