【问题标题】:Inconsistent behavior when using await with dynamic type将 await 与动态类型一起使用时的行为不一致
【发布时间】:2015-08-19 23:38:42
【问题描述】:

我正在尝试使用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-net ORM 之上的数据提供程序映射到底层 SQLite 表。

例如,如果我有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 的属性,它指定了所需的表(和具体类型)。

我需要/想要的是一段漂亮干净的代码,它可以从任何给定的表中获取实体。所以,下面的方法应该可以解决我所有的问题,但事实证明它并没有......

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,代码可能会或可能不会工作。这似乎真的取决于行星的位置和/或飞行意大利面怪物的情绪波动。

随机,但通常情况下,给定的线会抛出

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

我正在使用 .NET 4.0 扩展框架。

【问题讨论】:

  • 那里没有得到很好的回答 - 但仍然 - 如果您的 Repository 类型派生自可以返回延迟(抽象)GetAllAsync 的非泛型,您似乎可以避免动态存储库 实现。然后,您将有一个具体的点来调用,而不是动态存储库。
  • 可能你不应该首先在这里等待动态表达式。 await (Task&lt;IEnumerable&lt;T&gt;&gt;)repository.GetAllAsync() 有什么问题?这更好,因为它使用的动态更少。
  • 没有T,只有Type在运行时解析。我真的不知道如何从typeof(Schedule) 创建等效的(Task&lt;IEnumerable&lt;Schedule&gt;&gt;)。而且T 也不能是普通的Entity(抛出异常)。
  • 好的,使用await (Task)...。然后您可以使用dynamic 从该任务中提取结果。动态越少越好。

标签: c# .net async-await


【解决方案1】:

如果Repository&lt;T&gt; 类型是您自己创建的类型,您可以让它基于具有abstract Task&lt;IEnumerable&lt;Entity&gt;&gt; GetAllAsync() 的抽象基类型。然后,由于您的存储库显然已经具有该签名的方法-所以您很好:

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

然后让您的Repository&lt;Entity&gt; 基于存储库。

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
}

完全没有动态。

【讨论】:

  • 没关系。只要让你的班级成为Repository&lt;T&gt; : Repository where T: Entity。我(不清楚)说 - 两个类:RepositoryRepository&lt;T&gt;
  • 这可以做到,谢谢,稍作改动。抽象方法声明不能包含 async 关键字,GetAllEntitiesFrom 应返回 await repository.GetAllAsync()
  • 在我的(未经编辑的)问题中,我有点过于简单化了。我的约束实际上不是Entity,而是new()。因为这个代码是随机破坏的。谢谢你的努力。我仍然决定使用@Ivan Stoev 发布的方法,因为这对我来说不那么费力(无需添加任何抽象/具体类)。
  • 感谢 - 为后代编辑代码。我应该知道最好不要将意识流写入答案。我很高兴这个问题是从一个糟糕的副本中解救出来的。值得讨论。
【解决方案2】:

可以通过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&lt;TResult&gt; 是类,我们不能从协方差中受益。但是,我们可以借助一些通用扩展来做到这一点,但代价是一点 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);
        }
    }
}

【讨论】:

  • 但这不等同于我原来的工作但阻塞代码吗?
  • 否 - 请注意 await task;
  • 在我的(未经编辑的)问题中,我有点过于简单化了。我的约束实际上不是Entity,而是new()。因为这个代码是随机破坏的。这种方法效果很好,即使没有您添加的编辑部分:)
  • 是的,我知道。但是当有另一种方式时,我讨厌使用反射(和动态类似):-)
  • 我们一直都这样做:-) 但是请注意,如果T 不继承自Entity,则IEnumerable&lt;T&gt; 在这两种方法中都不能转换为IEnumerable&lt;Entity&gt;。这就是为什么第二种方法会在编译时保护您免受运行时意外的影响。
猜你喜欢
  • 1970-01-01
  • 2015-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-29
  • 2015-06-29
  • 2017-11-05
  • 2019-04-15
相关资源
最近更新 更多