Принудительно использовать метод асинхронного вызова
Скажем, у меня есть класс, который должен выполнить некоторую инициализацию 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
.