Как мне назвать метод Async методом Non-Async?

У меня есть метод ниже:

    public string RetrieveHolidayDatesFromSource() {
        var result = this.RetrieveHolidayDatesFromSourceAsync();
        /** Do stuff **/
        var returnedResult  = this.TransformResults(result.Result); /** Where result gets used **/
        return returnedResult;
    }


    private async Task<string> RetrieveHolidayDatesFromSourceAsync() {
        using (var httpClient = new HttpClient()) {
            var json = await httpClient.GetStringAsync(SourceURI);
            return json;
        }
    }

Вышеуказанное не работает и, похоже, не возвращает никаких результатов должным образом. Я не уверен, где мне не хватает заявления, чтобы заставить ждать результата? Я хочу, чтобы метод RetrieveHolidayDatesFromSource() возвращал строку.

Ниже работает отлично, но это синхронно, и я считаю, что его можно улучшить? Обратите внимание, что ниже синхронно, в котором я хотел бы перейти на асинхронный, но по какой-то причине мне не удалось обернуть голову.

    public string RetrieveHolidayDatesFromSource() {
        var result = this.RetrieveHolidayDatesFromSourceAsync();
        /** Do Stuff **/

        var returnedResult = this.TransformResults(result); /** This is where Result is actually used**/
        return returnedResult;
    }


    private string RetrieveHolidayDatesFromSourceAsync() {
        using (var httpClient = new HttpClient()) {
            var json = httpClient.GetStringAsync(SourceURI);
            return json.Result;
        }
    }

Я что-то упустил?

Примечание. По какой-то причине, когда я останавливаю вышеуказанный метод Async, когда он попадает в строку "var json = await httpClient.GetStringAsync(SourceURI)", он просто выходит из точки останова, и я не могу вернуться в метод.

Ответы

Ответ 1

Я что-то пропустил?

Да. Асинхронный код - по своей природе - подразумевает, что текущий поток не используется во время выполнения операции. Синхронный код - по своей природе - подразумевает, что текущий поток блокируется во время выполнения операции. Вот почему вызов асинхронного кода из синхронного кода буквально даже не имеет смысла. Фактически, как я описываю в своем блоге, наивный подход (с использованием Result/Wait) может легко привести к взаимоблокировкам.

Первое, что нужно учитывать: должен ли мой API быть синхронным или асинхронным? Если он касается ввода-вывода (как в этом примере), он должен быть асинхронным. Таким образом, это будет более подходящий дизайн:

public async Task<string> RetrieveHolidayDatesFromSourceAsync() {
    var result = await this.DoRetrieveHolidayDatesFromSourceAsync();
    /** Do stuff **/
    var returnedResult  = this.TransformResults(result); /** Where result gets used **/
    return returnedResult;
}

Как я описал в своей статье статью о лучшей асинхронной практике, вы должны пойти "асинхронно". Если вы этого не сделаете, вы все равно не получите какую-либо выгоду от асинхронизации, так зачем беспокоиться?

Но позвольте сказать, что вы заинтересованы в том, чтобы в конечном итоге перейти к async, но сейчас вы не можете все изменить, просто хотите изменить часть своего приложения. Это довольно распространенная ситуация.

В этом случае правильный подход заключается в том, чтобы разоблачать как синхронные, так и асинхронные API. В конце концов, после обновления всего кода, синхронные API-интерфейсы могут быть удалены. В статье о разработке асинхронных проектов на брандмауэре я исследую множество вариантов такого сценария; моим личным фаворитом является "bool parameter hack", который будет выглядеть следующим образом:

public string RetrieveHolidayDatesFromSource() {
  return this.DoRetrieveHolidayDatesFromSourceAsync(sync: true).GetAwaiter().GetResult();
}

public Task<string> RetrieveHolidayDatesFromSourceAsync() {
  return this.DoRetrieveHolidayDatesFromSourceAsync(sync: false);
}

private async Task<string> DoRetrieveHolidayDatesFromSourceAsync(bool sync) {
  var result = await this.GetHolidayDatesAsync(sync);
  /** Do stuff **/
  var returnedResult  = this.TransformResults(result);
  return returnedResult;
}

private async Task<string> GetHolidayDatesAsync(bool sync) {
  using (var client = new WebClient()) {
    return sync
        ? client.DownloadString(SourceURI)
        : await client.DownloadStringTaskAsync(SourceURI);
  }
}

Этот подход позволяет избежать дублирования кода, а также позволяет избежать любых проблем взаимоблокировки или повторного размещения, общих с другими решениями для устранения неполадок "sync-over-async".

Обратите внимание, что я все равно обработаю полученный код как "промежуточный шаг" на пути к правильно-асинхронному API. В частности, внутренний код должен был вернуться на WebClient (который поддерживает как синхронизацию, так и асинхронный) вместо предпочтительного HttpClient (который поддерживает только async). После того, как весь код вызова будет изменен для использования RetrieveHolidayDatesFromSourceAsync, а не RetrieveHolidayDatesFromSource, я бы пересмотрел его и удалил весь технический долг, изменив его на использование HttpClient и получив асинхронный доступ.

Ответ 2

public string RetrieveHolidayDatesFromSource() {
    var result = this.RetrieveHolidayDatesFromSourceAsync().Result;
    /** Do stuff **/
    var returnedResult  = this.TransformResults(result.Result); /** Where result gets used **/
    return returnedResult;
}

Если вы добавите .Result к асинхронному вызову, он выполнит и дождитесь, пока результат не появится, заставив его быть синхронным.

ОБНОВЛЕНИЕ:

private static string stringTest()
{
    return getStringAsync().Result;
}

private static async Task<string> getStringAsync()
{
    return await Task.FromResult<string>("Hello");
}
static void Main(string[] args)
{
    Console.WriteLine(stringTest());

}

Чтобы ответить на комментарий: Это работает без проблем.