HttpClient的异步任务管理

本文关键字:任务管理 异步 HttpClient | 更新日期: 2024-09-19 15:19:03

我正在创建一个通用加载程序,我想启动一个HttpClient SendAsync请求。然而,其中一些请求可能需要时间,所以我想添加取消选项,并在完成后通知。

这似乎是一个标准的场景。

我不确定这是否是正确的方法,但根据我看过的一些例子,我现在的处境是这样的。如果你看看代码的底部,我的问题是-在这一点上,我是否检查响应并引发成功或错误事件?

    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?
    }

HttpClient的异步任务管理

当您考虑公开与任务相关的状态(如IsFetching)时,只公开Task本身通常更干净、更容易。

类似这样的东西:

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

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

至于异步方法,您的方法应该返回一个结果。也许是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));

在等待http调用之后

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

您应该测试取消:

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

或者,您可以在一个调用中检查并抛出Microsoft异常:

cancel.ThrowIfCancellationRequested();