我有一个Singleton类,可以在其构造上加载一些数据。问题是加载这些数据需要调用async
方法,但构造函数不能是async
。
换句话说,我的班级有以下结构:
public class Singleton
{
private static Singleton instance;
private Singleton()
{
LoadData();
}
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
LoadData()
是一个async
函数,可以调用许多async
函数以及初始化。
如何正确调用LoadData()
以便一切正确初始化?
答案 0 :(得分:11)
问题是加载这些数据需要调用异步方法,但构造函数不能是异步的。
虽然您无法使构造函数本身异步,但可以从构造函数中调用异步方法。你不会马上得到结果。
如果异步方法返回Task
或Task<T>
,那么一旦异步操作完成,您总是可以使用任务的延续来在类中设置数据,或者只是阻塞结果,具体取决于在你的场景中最有意义的是什么。在不知道构造此对象的要求的情况下,很难知道在这种情况下适合的是什么。
编辑:
根据上面列出的目标,一个选项是更改Singleton
声明,以便检索Instance
的方法是方法,而不是属性。这将允许您使其异步:
public class Singleton
{
private static Singleton instance;
private Singleton()
{
// Don't load the data here - will be called separately
}
public static async Task<Singleton> GetInstance()
{
if (instance == null)
{
instance = new Singleton();
await instance.LoadData();
}
return instance;
}
}
这将允许您在调用上使用await
来实际检索实例。关于这一点的好处是它确实非常清楚你正在调用异步操作,并且你将得到正确的结果处理,因为结果将像任何其他异步方法一样返回。
但请注意,这不是线程安全的(虽然原始版本也不是),所以如果你要从多个线程使用这个Singleton,你可能不得不重新考虑整体设计。
另一种选择是让您的Singleton
课程不会自动加载数据。而是使从类中检索数据的方法异步。这提供了一些真正的优势,因为使用可能更加标准,并且您可以更容易地支持来自多个线程的调用(因为您可以控制数据加载过程),而不是通过制作它来处理它异步访问类实例。
答案 1 :(得分:9)
线程安全,异步单例 的解决方案实际上非常简单,如果我们只让Task
类的内部机制为我们工作!
那么,Task
如何运作?假设您有Task<T>
的实例,并且您await
一次。现在执行任务,生成值T
并返回给您。如果再次await
相同的任务实例怎么办?在这种情况下,任务只是以完全同步的方式立即返回先前生成的值。
如果你await
同一个任务实例同时来自多个线程(你通常会遇到竞争条件)怎么办?好吧,第一个(因为将成为首先到达那里的那个)将执行任务代码,而其他人将等待处理结果。然后,当结果生成时,所有await
将同时(几乎)完成并返回值。
因此,线程安全的async
单例的解决方案实际上非常简单:
public class Singleton
{
private static readonly Task<Singleton> _getInstanceTask = CreateSingleton();
public static Task<Singleton> Instance
{
get { return _getInstanceTask; }
}
private Singleton(SomeData someData)
{
SomeData = someData;
}
public SomeData SomeData { get; private set; }
private static async Task<Singleton> CreateSingleton()
{
SomeData someData = await LoadData();
return new Singleton(someData);
}
}
现在您可以通过这种方式访问单身人士:
Singleton mySingleton = await Singleton.Instance;
或
Singleton mySingleton = Singleton.Instance.Result;
或
SomeData mySingletonData = (await Singleton.Instance).SomeData;
或
SomeData mySingletonData = Singleton.Instance.Result.SomeData;
在此处阅读更多内容:Async singleton initialization
答案 2 :(得分:6)
您可以使用asynchronous lazy initialization:
public class Singleton
{
private static readonly AsyncLazy<Singleton> instance =
new AsyncLazy<Singleton>(CreateAndLoadData);
private Singleton()
{
}
// This method could also be an async lambda passed to the AsyncLazy constructor.
private static async Task<Singleton> CreateAndLoadData()
{
var ret = new Singleton();
await ret.LoadDataAsync();
return ret;
}
public static AsyncLazy<Singleton> Instance
{
get { return instance; }
}
}
然后你可以像这样使用它:
Singleton singleton = await Singleton.Instance;
使用AsyncLazy<T>
的一个好处是它是线程安全的。但是,请注意它始终在线程池线程上执行其委托。
答案 3 :(得分:1)
嗯,你想要异步初始化一个单例并没有多大意义。如果您只想调用在初始化中返回Task的方法,则可以执行以下操作:
var task = MyAsyncMethod();
task.Wait();
return task.Result;
无需制作方法async
。
但是,如果你想要的是将单例值作为任务,你可以使用Lazy:
Lazy<Task<int>> l = new Lazy<Task<int>>(async () => { int i = await calculateNumber(); return i; });
此外,Lazy<T>
是实施“单身人士”的首选方法。单身人士课程难以正确(或难以保持正确)......