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

本文关键字:不一致 await 动态 类型 | 更新日期: 2023-09-27 18:08:39

我正在尝试使用dynamic来绕过由于设计或缺乏它而造成的不便(如果感兴趣,可以在这里找到"不便")。

简而言之,我需要返回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的属性,它指定了所需的表(和具体类型)。

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

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扩展框架

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

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

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

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

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

然后,在使用dynamic时,你可以说:

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
}

完全没有动力。

可以通过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垃圾。但在我看来,它极大地简化了这样的设计/实现。查看一下:

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);
        }
    }
}