使用await与动态类型时的行为不一致

时间:2015-08-18 19:52:56

标签: c# .net async-await

我正在尝试使用dynamic来解决因设计或缺乏而导致的不便(如果感兴趣Simplify method retrieving data from generic repository,可以在此处找到“不方便”)。

为了缩短它,我需要返回Entity个实例的集合。上课非常简单:

[JsonObject]
public class Entity
{
    [PrimaryKey]
    [JsonProperty(PropertyName = "id")]
    public virtual int Id { get; set; }

    [JsonIgnore]
    public string Content { get; set; }
}

因此Entity只有IdContent。继承类可能有其他属性,但我只对Content部分(复杂JSON)感兴趣。

可以通过通用Repository<T>访问各种不同的实体。我需要知道具体类的Type,因为T通过数据提供程序映射到基础SQLite表,构建在SQLite-net ORM之上。

因此,例如,如果我有Schedule : Entity,那么我将使用Repository<Schedule>来操作名为Schedule的表。这部分工作正常。

// must be instantiated with concrete class/type inheriting
// from Entity in order to map to correct database table
public class Repository<T> where T : new()
{
    public async virtual Task<IEnumerable<T>> GetAllAsync()
    {
        return await SQLiteDataProvider.Connection.Table<T>().ToListAsync();
    }
    // etc.
}

主要问题是“命令”来自JavaScript客户端,因此我将收到JSON格式的请求。在这个JSON中,我有一个名为CollectionName的属性,它指定了所需的表(以及具体类型)。

我需要/想要的是一个很好的&amp;干净的代码片段,可以从任何给定的表中获取实体。因此,下面的方法应该解决我所有的问题,但事实证明它没有......

public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
    // args.CollectionName is type of entity as string
    // namespace + collection name is mapped as correct type
    // e.g. MyNamespace.Schedule
    Type entityType = Type.GetType(
        string.Format("{0}{1}", EntityNamespacePrefix, args.CollectionName), true, true);

    // get correct repository type using resolved entity type
    // e.g. Repository<MyNamespace.Schedule>
    Type repositoryType = typeof(Repository<>).MakeGenericType(entityType);
    dynamic repository = Activator.CreateInstance(repositoryType);

    // Below `GetAllAsync()` returns `Task<IEnumerable<T>>`.

    // this blocking call works 100%
    //var entities = repository.GetAllAsync().Result;

    // this non-blocking call works when it feels like it
    var entities = await repository.GetAllAsync();

    return entities;
}

因此,如果(上图)我使用阻止.Result,那么一切都有吸引力。相反,如果我使用await,代码可能会也可能不会。这似乎取决于Flying Spaghetti Monster的行星位置和/或情绪波动。

随机地,但通常情况下,给定的行将抛出

  

无法投射类型的对象   'System.Runtime.CompilerServices.TaskAwaiter'1 [System.Collections.Generic.IEnumerable'1 [MyNamespace.Schedule]]'   输入'System.Runtime.CompilerServices.INotifyCompletion'。

我正在使用.NET 4.0 Extended Framework。

2 个答案:

答案 0 :(得分:3)

如果Repository<T>类型是您自己制作的类型,则可以使其基于具有abstract Task<IEnumerable<Entity>> GetAllAsync()的抽象基类型。然后,由于您的存储库显然已经有一个签名方法 - 所以你很好:

public abstract class Repository
{
  public abstract Task<IEnumerable<Entity>> GetAllAsync();
}

然后让您的Repository<Entity>基于存储库。

public class Repository<T>: Repository where T: Entity  // Your existing class
{
  public override async Task<IEnumerable<Entity>> GetAllAsync()
  {
    //  Your existing implementation
  }
  //...existing stuff...
}

然后,当使用它而不是动态时,你可以说:

public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
  var entityType = 
    Type.GetType(
      string.Format(
        "{0}{1}", 
        EntityNamespacePrefix, 
        args.CollectionName), 
      true, 
      true);

  var repositoryType =
    typeof(Repository<>)
    .MakeGenericType(entityType);

  var repository = 
    (Repository) Activator
    .CreateInstance( repositoryType );

  return repository.GetAllAsync();  // await not required
}

根本没有动态。

答案 1 :(得分:1)

可以通过2次动态调用来实现:

public async Task<IEnumerable<Entity>> GetAllEntitiesFrom(CollectionArgs args)
{
    var entityType = Type.GetType(
        string.Format("{0}{1}", EntityNamespacePrefix, args.CollectionName), true, true);
    var repositoryType = typeof(Repository<>).MakeGenericType(entityType);
    var repository = Activator.CreateInstance(repositoryType);
    var task = (Task)((dynamic)repository).GetAllAsync();
    await task;
    var entities = (IEnumerable<Entity>)((dynamic)task).Result;
    return entities;
}  

修改
虽然上述应该有效,但应该有更好的整体设计。不幸的是,MS决定使用任务进行异步,因为Task<TResult>是类,我们不能从协方差中获益。但是,我们可以借助一点通用扩展来实现这一点,并且需要一点GC垃圾的成本。但IMO极大地简化了这种设计/实现。看看:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace Tests
{
    // General async extensions
    public interface IAwaitable<out TResult>
    {
        IAwaiter<TResult> GetAwaiter();
        TResult Result { get; }
    }
    public interface IAwaiter<out TResult> : ICriticalNotifyCompletion, INotifyCompletion
    {
        bool IsCompleted { get; }
        TResult GetResult();
    }
    public static class AsyncExtensions
    {
        public static IAwaitable<TResult> AsAwaitable<TResult>(this Task<TResult> task) { return new TaskAwaitable<TResult>(task); }
        class TaskAwaitable<TResult> : IAwaitable<TResult>, IAwaiter<TResult>
        {
            TaskAwaiter<TResult> taskAwaiter;
            public TaskAwaitable(Task<TResult> task) { taskAwaiter = task.GetAwaiter(); }
            public IAwaiter<TResult> GetAwaiter() { return this; }
            public bool IsCompleted { get { return taskAwaiter.IsCompleted; } }
            public TResult Result { get { return taskAwaiter.GetResult(); } }
            public TResult GetResult() { return taskAwaiter.GetResult(); }
            public void OnCompleted(Action continuation) { taskAwaiter.OnCompleted(continuation); }
            public void UnsafeOnCompleted(Action continuation) { taskAwaiter.UnsafeOnCompleted(continuation); }
        }
    }
    // Your entity framework
    public abstract class Entity
    {
        // ...
    }
    public interface IRepository<out T>
    {
        IAwaitable<IEnumerable<T>> GetAllAsync();
    }
    public class Repository<T> : IRepository<T> where T : Entity
    {
        public IAwaitable<IEnumerable<T>> GetAllAsync() { return GetAllAsyncCore().AsAwaitable(); }
        protected async virtual Task<IEnumerable<T>> GetAllAsyncCore()
        {
            //return await SQLiteDataProvider.Connection.Table<T>().ToListAsync();

            // Test
            await Task.Delay(1000);
            return await Task.FromResult(Enumerable.Empty<T>());
        }
    }
    public static class Repository
    {
        public static IAwaitable<IEnumerable<Entity>> GetAllEntitiesFrom(string collectionName)
        {
            var entityType = Type.GetType(typeof(Entity).Namespace + "." + collectionName, true, true);
            var repositoryType = typeof(Repository<>).MakeGenericType(entityType);
            var repository = (IRepository<Entity>)Activator.CreateInstance(repositoryType);
            return repository.GetAllAsync();
        }
    }
    // Test
    class EntityA : Entity { }
    class EntityB : Entity { }
    class Program
    {
        static void Main(string[] args)
        {
            var t = Test();
            t.Wait();
        }
        static async Task Test()
        {
            var a = await Repository.GetAllEntitiesFrom(typeof(EntityA).Name);
            var b = await Repository.GetAllEntitiesFrom(typeof(EntityB).Name);
        }
    }
}