2013-12-12 104 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();