Как правильно переносить асинхронные http-запросы в успешные и неудачные обратные вызовы - PullRequest
0 голосов
/ 19 апреля 2019

Я выполняю рефакторинг старого кода, который выполняет синхронные http-запросы и возвращает объект Callback с событиями успеха и сбоя.Как правильно обернуть код в async / await?

Я добавил класс HttpClient и использую метод SendAsync, для которого я ожидаю, но я не уверен, как правильно сделать переход из await в события.Я добавил асинхронный void метод Execute в класс, но это не похоже на правильный способ обработки - избегайте асинхронного 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(()=>executor.ExecuteAsync());

Кажется, что это немного пожар и забыть, но с обратными вызовами (я жду ответа от сети).Как правильно с этим справиться?

1 Ответ

2 голосов
/ 19 апреля 2019

Я выполняю рефакторинг старого кода, который выполняет синхронные http-запросы и возвращает объект Callback с событиями успеха и сбоя.Как правильно обернуть код в async / await?

Вы полностью избавляетесь от обратных вызовов.

Сначала рассмотрим случай сбоя.(ErrorType, string) должно быть преобразовано в пользовательский Exception:

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

  ...
}

Затем вы можете смоделировать Succeed / Failed обратных вызовов как один Task<string>:

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