Принудительно использовать метод асинхронного вызова

Скажем, у меня есть класс, который должен выполнить некоторую инициализацию async, используя метод InitializeAsync(). Я хочу убедиться, что инициализация выполняется только один раз. Если другой поток вызывает этот метод, пока инициализация уже выполняется, он будет "ждать", пока не вернется первый вызов.

Я думал о следующем: (с помощью SemaphoreSlim). Есть ли лучший/более простой подход?

public class MyService : IMyService
{
    private readonly SemaphoreSlim mSemaphore = new SemaphoreSlim(1, 1);
    private bool mIsInitialized;

    public async Task InitializeAsync()
    {
        if (!mIsInitialized)
        {
            await mSemaphore.WaitAsync();

            if (!mIsInitialized)
            {
                await DoStuffOnlyOnceAsync();
                mIsInitialized = true;
            }

            mSemaphore.Release();
        }
    }

    private Task DoStuffOnlyOnceAsync()
    {
        return Task.Run(() =>
        {
            Thread.Sleep(10000);
        });
    }
}

Спасибо!

Edit:

Поскольку я использую DI, и эта услуга будет введена, ее использование как "Lazy" ресурса или использование async factory не будет работать для меня (хотя это может быть здорово в других случаях использования). Таким образом, инициализация async должна быть инкапсулирована внутри класса и прозрачна для потребителей IMyService.

Идея обернуть код инициализации в объекте "dummy" AsyncLazy<> будет выполнять эту работу, хотя для меня это немного неестественно.

Ответ 1

Я бы пошел с AsyncLazy<T> (слегка измененная версия):

public class AsyncLazy<T> : Lazy<Task<T>> 
{ 
    public AsyncLazy(Func<T> valueFactory) : 
        base(() => Task.Run(valueFactory)) { }

    public AsyncLazy(Func<Task<T>> taskFactory) : 
        base(() => Task.Run(() => taskFactory()) { } 

    public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); } 
}

И используйте его так:

private AsyncLazy<bool> asyncLazy = new AsyncLazy<bool>(async () =>
                                    { 
                                        await DoStuffOnlyOnceAsync()
                                        return true;
                                    });

Примечание. Я использую bool просто потому, что у вас нет типа возврата из DoStuffOnlyOnceAsync.

Edit:

Стефан Клири (конечно) также имеет реализацию этого здесь.

Ответ 2

Да. Используйте Stephen Cleary AsyncLazy (доступно на AsyncEx nuget):

private static readonly AsyncLazy<MyResource> myResource = new AsyncLazy<MyResource>(
    async () => 
    { 
        var ret = new MyResource(); 
        await ret.InitAsync(); 
        return ret; 
    }
);

public async Task UseResource()
{
    MyResource resource = await myResource;
    // ...
}

Или visual studio SDK AsyncLazy, если вы предпочитаете реализацию Microsoft.

Ответ 3

У меня есть сообщение в блоге, которое охватывает несколько разных вариантов для создания "асинхронных конструкторов" .

Обычно я предпочитаю асинхронные методы factory, потому что я думаю, что они проще и немного безопаснее:

public class MyService
{
  private MyService() { }

  public static async Task<MyService> CreateAsync()
  {
    var result = new MyService();
    result.Value = await ...;
    return result;
  }
}

AsyncLazy<T> - отличный способ определить общий асинхронный ресурс (и может быть лучшим концептуальным соответствием для "службы", в зависимости от того, как он используется). Единственным преимуществом метода async factory является то, что невозможно создать неинициализированную версию MyService.