我经常从数据库中加载对象。通常我会在需要时加载对象,在构造函数中我调用数据库并填充我需要的所有方法。
我为加速这个过程所做的是在构造函数中创建一个任务,并在我在getter中返回值之前使用wait。例如
在:
class Foo{
public string Bar {get;set;}
public Foo(int id){
DataRow res;
//Do database operations
Bar = res["Bar"].ToString()
}
}
现在:
class Foo{
private Task LoadTask;
private string _Bar;
public string Bar {
get {
LoadTask.Wait();
return _Bar;
}
set {
_Bar = value;
}
}
public Foo(int id){
LoadTask = Task.Factory.StartNew(() => {
DataRow res;
//Do database operations
Bar = res["Bar"].ToString();
});
}
}
我想要做的是扩展一个类,在构造函数中它触发此任务,在子类中调用重写的方法,然后在任务完成之前阻止任何属性的获取。
我发现的最多的是这个,但不确定它是否符合我的要求
http://www.gutgames.com/post/Overridding-a-Property-With-ReflectionEmit.aspx
答案 0 :(得分:3)
正如我之前所说,我认为这个设计可以改进,但我很欣赏技术挑战,所以我试了一下。
你所谈论的内容与Entity Framework的动态创建的更改跟踪代理并没有太大的不同,所以我快速浏览了一下与动态代理一起使用的框架,并快速确定了Castle Project(http://www.nuget.org/packages/Castle.Core )作为我的首选武器。
天真的实施
这是我们现阶段的目标:
Foo foo = Foo.Factory.Create<Foo>();
foo.Bar = "Zzz"; // Runs immediately.
string bar = foo.Bar; // Blocks until initialisation has completed.
让我们暂时遗漏继承(假装Foo
已被封存)。
我们希望Foo
没有公共构造函数强制使用者通过Foo.Factory.Create<Foo>()
实例化它,它返回一个从Foo
派生的动态代理,并在每个虚拟中注入一些额外的功能property getter invocation:等待初始化任务完成。
using System.Collections.Generic;
using System.Threading.Tasks;
using Castle.DynamicProxy;
public class Foo
{
// Fields.
protected readonly List<Task> InitialisationTasks = new List<Task>();
// Properties.
// These have to be declared virtual
// in order for dynamic proxying to work.
public virtual string Bar { get; set; }
protected Foo()
{
// Initialisation work.
this.InitialisationTasks.Add(Task.Delay(500));
}
// Responsible for instantiating dynamic
// proxies of Foo and its derivatives.
public static class Factory
{
// Static fields.
static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator();
static readonly WaitForInitInterceptor Interceptor = new WaitForInitInterceptor();
// Factory method.
public static T Create<T>() where T : Foo
{
return ProxyGenerator.CreateClassProxy<T>(Interceptor);
}
class WaitForInitInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
// Applies to getters only.
if (invocation.Method.Name.StartsWith("get_"))
{
var foo = invocation.InvocationTarget as Foo;
if (foo != null)
{
// Block until initialisation completes.
Task.WhenAll(foo.InitialisationTasks).Wait();
}
// Continue to the target method.
invocation.Proceed();
}
}
}
}
}
到目前为止一直很好,但听到它的声音,我们还必须处理继承问题。现有设计不支持这一点,因为:
Foo.Factory.Create<Foo>()
- 我们需要禁止它。virtual
,以便代理可以拦截它们的getter调用。调整以支持继承
对救援的反思:
public class Foo
{
// Fields.
protected readonly List<Task> InitialisationTasks = new List<Task>();
// Properties.
// These have to be declared virtual
// in order for dynamic proxying to work.
public virtual string Bar { get; set; }
protected Foo()
{
// Enforce proxy integrity.
this.Validate();
// Initialisation work.
this.InitialisationTasks.Add(Task.Delay(500));
}
private void Validate()
{
var type = ProxyUtil.GetUnproxiedType(this);
// No public constructors.
if (type.GetConstructors().Length != 0)
{
throw new InvalidOperationException(
"Public constructors not supported in derived types."
);
}
// No non-virtual properties.
foreach (var property in type.GetProperties())
{
// We're only interested in getters.
var method = property.GetGetMethod();
if (method != null && !method.IsVirtual)
{
throw new InvalidOperationException(
"Only virtual properties are supported."
);
}
}
}
// Responsible for instantiating dynamic
// proxies of Foo and its derivatives.
public static class Factory
{
// Static fields.
static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator();
static readonly WaitForInitInterceptor Interceptor = new WaitForInitInterceptor();
// Factory method.
public static T Create<T>() where T : Foo
{
return ProxyGenerator.CreateClassProxy<T>(Interceptor);
}
class WaitForInitInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
// Applies to getters only.
if (invocation.Method.Name.StartsWith("get_"))
{
var foo = invocation.InvocationTarget as Foo;
if (foo != null)
{
// Block until initialisation completes.
Task.WhenAll(foo.InitialisationTasks).Wait();
}
// Continue to the target method.
invocation.Proceed();
}
}
}
}
}
现在,如果我们要创建具有公共构造函数或非虚拟属性的class FooDerived : Foo
(不具有getter的属性不受此规则限制),则基础构造函数将因此强制执行消费者使用Foo.Factory.Create<FooDerived>()
。
如果FooDerived
需要执行自己的异步初始化工作,它只需将自己的任务添加到InitialisationTasks
- 任何属性获取器都将阻塞,直到所有已完成
由于每个&#39; Foo&#39;这个代码有点粗糙。代理初始化做了很多密集的工作(通过Validate
)。在一个理想的世界里,我会有一些已经通过验证的类型的缓存(也许是一个字典),并跳过那些缓慢的反射绑定验证。
替代方法
虽然动态代理很有趣,但设计存在缺陷。这些担忧并没有很好地分开。 Foo
首先不应该担心拔出自己的数据,绝对不应该担心Task
,线程池等。在评论中对此进行了广泛讨论,我认为最好的办法是在有足够信息的情况下启动数据加载任务,保存Task
引用(或其他任何异步单元)当你需要使用完全加载的实例时,等待它们(或通过获取Result
或调用Wait
来阻止)。这可确保在加载完成之前无法访问Foo
实例,并使您可以合理控制异步对象加载的计划方式。例如,您可以滚动自己的有限并发调度程序,或使用ConcurrentExclusiveSchedulerPair
s ExclusiveScheduler
来确保您不会充满工作线程池。批处理对象加载(例如,使用Task<IEnumerable<Foo>>
而不是IEnumerable<Task<Foo>>
)是另一种密切关注您创建的任务数量的好方法。一旦将它与对象构造逻辑分离,就可以很容易地通过异步加载获得创意,并且几乎可以肯定这是正确的方法。