在异步任务完成时阻止getter

时间:2014-06-23 04:00:28

标签: c# reflection getter-setter

我经常从数据库中加载对象。通常我会在需要时加载对象,在构造函数中我调用数据库并填充我需要的所有方法。

我为加速这个过程所做的是在构造函数中创建一个任务,并在我在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

1 个答案:

答案 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>>)是另一种密切关注您创建的任务数量的好方法。一旦将它与对象构造逻辑分离,就可以很容易地通过异步加载获得创意,并且几乎可以肯定这是正确的方法。