C# — Как присвоить результат GetFromJsonAsync переменной и использовать его без повторного вызова запроса GET?

#c# #http #asp.net-core #asynchronous #get

Вопрос:

Я пытаюсь присвоить свойству класса результат запроса GET, чтобы все другие методы в классе могли использовать значение этого свойства без необходимости вызывать запрос GET более одного раза. Суть в том, что я хочу вызвать запрос GET во время создания экземпляра класса, назначить его переменной, а затем никогда больше не вызывать его во время жизни объектов.

 public class Example {
  
  private readonly HttpClient _http;
  private readonly List<Thing> _things;
  
  public Example(HttpClient http) 
  {
    _http = http;
    _things = _http.GetFromJsonAsync<List<Thing>>("https://api-to-call/endpoint").Resu<
  }

  public void UseThings()
  {
    // Do something with _things;
  }

}
 

Однако при вызове метода он отправляет новый запрос GET для получения обновленного значения свойства, которое я назначил в конструкторе. Как я могу закодировать это так, чтобы он вызывал запрос GET только один раз во время создания экземпляра объекта?

Комментарии:

1. Вы не должны выполнять асинхронные вызовы в конструкторе. Вместо этого вы должны сделать это в другом месте потока вашей программы после создания объекта, например, создав UseThings метод async и выполнив его там.

2. Как правило, и предполагая , что он не является изменчивым, вы бы кэшировали его где-нибудь (истекает срок действия или нет, зависит от конкретного пользователя/запроса или нет, в зависимости от вашего варианта использования/данных) и только при необходимости обновляли его соответствующим образом.

3. Как вы, вероятно, уже поняли, вы не можете сделать конструктор класса асинхронным. Не заходя в кроличью нору при перепроектировании вашего решения, вы могли бы просто сохранить Task его как члена класса, а затем в своих методах ожидать его результата.

4. Видишь это: blog.stephencleary.com/2013/01/async-oop-2-constructors.html

5. UseThings() не выполняет GET снова.

Ответ №1:

Вы не должны блокировать в конструкторе и .Результат может иметь неприятные побочные эффекты. Хотя уже обсуждались языковые функции для поддержки этого, до тех пор вам следует перенести это в кэшированную операцию. Вы можете убедиться, что операция выполняется только один раз, заключив http-вызов в a SemaphoreSlim .

 public class Example
{
    private readonly HttpClient _http;
    private static readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
    private List<Thing> _things;

    public Example(HttpClient http)
    {
        _http = http;
    }

    public async Task UseThings()
    {
        // Do something with _things;
        var localThings = _things ?? await GetThingsAsync();
    }
    
    private async Task<List<Thing>> GetThingsAsync()
    {
        if (_things != null)
        {
            return await Task.FromResult(_things);
        }
        
        await _lock.WaitAsync();
        try
        {
            // double check in case another thread has completed
            if (_things != null)
            {
                return _things;
            }
            
            _things = await _http.GetFromJsonAsync<List<Thing>>("https://api-to-call/endpoint");
            return _things;
        }
        finally 
        {
            _lock.Release();
        }   
    }
}
 

Комментарии:

1. Что вы думаете о том, чтобы сделать оба свойства статическими? т. е. «частный статический HttpClient _http = новый()» и «частная статическая задача<Список<Вещь><Вещь>> = _http. GetFromJsonAsync<Список<Вещь><Вещь>>(«<Вещь>> api для вызова/конечная точка» )». Это помогает предотвратить дополнительные поездки к конечной точке с наименьшим количеством кода.

2. Это заблокирует и, в зависимости от контекста, может привести к тупику. Кроме того, поскольку он статичен, он может даже блокировать запуск приложения, если граф объектов создается во время инициализации.