【问题标题】:How properly wrap async http requests into success and fail callbacks如何正确地将异步 http 请求包装成成功和失败回调
【发布时间】:2019-04-19 20:14:28
【问题描述】:

我正在重构执行同步 http 请求并返回带有成功和失败事件的回调对象的旧代码。如何正确地将代码包装到 async/await 中?

我已经添加了 HttpClient 类,并且我正在使用我等待的 SendAsync 方法,但我不确定如何正确地从等待过渡到事件。我在类中添加了 async void Execute 方法,但它似乎不是正确的处理方式 - 避免使用 async void。下面是(短版)代码中的更多解释。


public class HttpExecutor(){

    public event Action<string> Succeed;
    public event Action<ErrorType, string> Failed;
    private bool isExecuting;

    //I know that async void is not the best because of exceptions
    //and code smell when it is not event handler
    public async void Execute()
        {
            if (isExecuting) return;

            isExecuting = true;
            cancellationTokenSource = new CancellationTokenSource();

            try
            {
                httpResponseMessage =
                    await httpService.SendAsync(requestData, cancellationTokenSource.Token).ConfigureAwait(false);

                var responseString = string.Empty;
                if (httpResponseMessage.Content != null)
                {
                    responseString = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
                }

                if (httpResponseMessage.IsSuccessStatusCode)
                {
                    Succeed?.Invoke(responseString);
                    return;
                }

                Failed?.Invoke(httpResponseMessage.GetErrorType(),
                    $"{httpResponseMessage.ReasonPhrase}\n{responseString}");
            }
            //Catch all exceptions separately
            catch(...){
            }
            finally
            {
                Dispose();
            }
        }
}

public class UserService(){

    public CallbackObject<User> GetUser(){
        var executor = new HttpExecutor(new RequestData());
        //CallbackObject has also success and fail, and it hooks to executor events, deserializes string into object and sends model by his own events.
        var callback = new CallbackObject<User>(executor);
        executor.Execute();//in normal case called when all code has possibility to hook into event
        return callback;
    }

}

我觉得我应该将方法更改为:public async Task ExecuteAsync(){...},但是我需要通过执行以下操作从线程池中获取线程:Task.Run(()=&gt;executor.ExecuteAsync());

这似乎有点过时了,但有回调(我等待来自网络的响应)。如何正确处理?

【问题讨论】:

  • 在体验了 async/await 的精彩之后,为什么还要回到回调? :-)。
  • 您有什么理由需要这些操作吗?最初的用例是释放 UI 以提高响应能力吗?
  • "但是我需要从线程池中获取线程" - 为什么?

标签: c# asynchronous async-await request void


【解决方案1】:

我正在重构执行同步 http 请求并返回带有成功和失败事件的回调对象的旧代码。如何正确地将代码包装到 async/await 中?

你完全摆脱了回调。

首先,考虑失败案例。 (ErrorType, string) 应该做成自定义的Exception

public sealed class ErrorTypeException : Exception
{
  public ErrorType ErrorType { get; set; }

  ...
}

然后您可以将 Succeed / Failed 回调建模为单个 Task&lt;string&gt;

public async Task<string> ExecuteAsync()
{
  if (isExecuting) return;
  isExecuting = true;

  cancellationTokenSource = new CancellationTokenSource();
  try
  {
    httpResponseMessage = await httpService.SendAsync(requestData, cancellationTokenSource.Token).ConfigureAwait(false);
    var responseString = string.Empty;
    if (httpResponseMessage.Content != null)
    {
      responseString = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
    }

    if (httpResponseMessage.IsSuccessStatusCode)
      return responseString;

    throw new ErrorTypeException(httpResponseMessage.GetErrorType(),
        $"{httpResponseMessage.ReasonPhrase}\n{responseString}");
  }
  catch(...){
    throw ...
  }
  finally
  {
    Dispose();
  }
}

用法:

public Task<User> GetUserAsync()
{
  var executor = new HttpExecutor(new RequestData());
  var text = await executor.ExecuteAsync();
  return ParseUser(text);
}

【讨论】:

  • 嗨斯蒂芬,非常感谢您的快速回复和帮助!我认为您的回答使我满意,但我只需要澄清一下。我会尽力解释我最好的。从现在开始,每当我想调用 GetUserAsync() 时,最好的方法是将 GetUserAsync() 的调用者重构为等待/异步 - 像病毒一样传播:)。它什么时候停止,或者如何启动整个异步调用流程?在这一点上,我不确定我是否能够将所有方法重构为异步等待。我应该从 Task.Run(()=> GetUserAsync()) 开始吗?Ps.我有你的书,但最近没有太多时间阅读它;(
  • @AAGames:最好的办法是让它成长;这个原则的名字是"async all the way"。如果您需要进行部分翻译,您可以考虑在Task&lt;T&gt; 周围编写一个类似执行器的包装器,这样您的部分代码仍然可以使用事件。当然是暂时的——你不想让两种不同的异步模式保持很长时间。
猜你喜欢
  • 1970-01-01
  • 2014-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-08
  • 1970-01-01
  • 2021-01-28
  • 1970-01-01
相关资源
最近更新 更多