Асинхронная инициализация в C#: преодоление ограничений конструктора

Асинхронная инициализация в C#: преодоление ограничений конструктора

28 декабря 2023 г.

В чем проблема?

Синтаксис асинхронного ожидания дает нам самый простой способ написания асинхронного кода.

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

Существует множество причин, по которым это невозможно в существующей языковой версии. Вероятно, одним из очевидных является то, что асинхронные методы будут возвращать Task или Task<T>, чтобы иметь возможность правильно обрабатывать исключения без сбоя процесса и тривиально иметь возможность дождаться завершения асинхронной операции.

А если мы просто возьмем следующий класс, создадим содержащую его библиотеку, а затем посмотрим на дизассемблированный код.

public class MyObject
{
    public MyObject()
    {
        //diligently initializing
    }
}

Мы увидим следующую строку.

instance void [System.Runtime]System.Object::.ctor()

где ключевое слово instance указывает, что метод является методом экземпляра, то есть он вызывается для экземпляра класса, а не для самого класса (на что указывает ключевое слово static).

То есть технически он вызывает некоторый метод (конструктор) уже существующего объекта для его инициализации.

Можно ли гипотетически сделать его асинхронным?

public class MyObject
{
    public async MyObject()
    {
        await InitializeAsync();
    }
}

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

Я считаю, что разработчики языка C# много раз обсуждали этот вопрос и четко понимают осуществимость и значимость включения этого изменения.< /п>

Однако даже без встроенной в язык такой возможности мы можем решить эту проблему.

Способы преодоления

Существует множество подходов, которые можно использовать для достижения асинхронной инициализации.

Асинхронизация

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

public class MyBestObject
{
    public MyBestObject()
    {
        InitializeAsync().GetAwaiter().GetResult();
    }

    private async Task InitializeAsync()
    {
        //diligently initializing

        await Task.Delay(100);
    }
}

Этот подход не так уж плох, если нет контекста синхронизации и асинхронная операция выполняется относительно быстро, но в целом это не рекомендуемая практика, поскольку она основана на множестве предположений о среде выполнения и деталях. асинхронной операции. Это приводит к неэффективному потреблению ресурсов, внезапным блокировкам в приложениях пользовательского интерфейса и распространенному нарушению идеи асинхронного программирования == «полная асинхронность». ==

Асинхронная фабрика

Вполне стандартный способ решения этой проблемы — использование фабрик.

public class MyBestService
{
    public async Task InitializeAsync() 
    {
        //diligently initializing

        await Task.Delay(100);
    }
}

public interface IMyBestServiceFactory
{
    Task<MyBestService> CreateAsync(CancellationToken cancellationToken);
}

public sealed class MyBestServiceFactory : IMyBestServiceFactory
{
    public MyBestServiceFactory()
    {
    }

    public async Task<MyBestService> CreateAsync(CancellationToken cancellationToken)
    {
        var service = new MyBestService();
        await service.InitializeAsync(cancellationToken);
        return service;
    }
}

Мы могли бы либо использовать статический метод в классе MyBestService, либо даже указать для этой цели специальную фабрику. Второй вариант немного более совместим с шаблоном Внедрение зависимостей, поскольку вы можете запросить IMyBestServiceFactory в любом классе, а затем просто вызвать CreateAsync метод. .

Основным недостатком этого подхода является дополнительная связь, поскольку вам (любой класс использует IMyBestServiceFactory) необходимо контролировать время жизни вновь созданного объекта.

Кроме того, требуется адаптация решений, использующих отражение (Activate.CreateInstace) или выражения (Expression.New) для создания и инициализации экземпляров.

Асинхронная инициализация

Мы могли бы использовать следующий трюк, чтобы избежать проблем с шаблоном Async Factory.

public class MyBestService
{
    private readonly Task _initializeTask;

    public MyBestService()
    {
        _initializeTask = InitializeAsync();
    }

    public async Task DoSomethingAsync() 
    {
        await _initializeTask;

        // Do something async
    }

    private async Task InitializeAsync()
    {
        //diligently initializing

        await Task.Delay(100);
    }
}

Как видите, мы начинаем асинхронную инициализацию в конструкторе и сохраняем ссылку на запущенную задачу. Затем, прежде чем выполнять какую-либо значимую операцию, мы проверяем, что _initializeTask завершен, просто ожидая его.

Использование этого подхода очень распространено как в случаях с самостоятельным созданием объектов, так и в случае IoC-контейнера.

var myBestService = new MyBestService();
await myBestService.DoSomethingAsync();

Однако у этого подхода есть несколько недостатков:

  • Отсутствие контроля над процессом инициализации. Например, мы не можем просто передать CancellationToken.
  • Он на некоторое время дает вызывающему объекту не полностью созданный экземпляр. Хотя в целом это не кажется проблемой, это может привести к усложнению отладки, если операция инициализации занимает слишком много времени. Кроме того, время ожидания операции вызова может быть меньше требуемого времени для этого конкретного объекта, что, очевидно, приведет к проблемам во время выполнения.

Заключение

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

https://github.com/alex-popov-stenn/CSharpAsyncInitPatterns/tree/main

Спасибо за чтение! Увидимся в следующий раз!


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE