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

Mik*_*ala 5 .net c# async-await

我试图用来dynamic解决由于设计或缺乏而造成的不便(如果感兴趣的是简化方法从通用存储库中检索数据,可以在这里找到"不便" ).

为了缩短它,我需要返回Entity实例的集合.类非常简单:

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

    [JsonIgnore]
    public string Content { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

所以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.
}
Run Code Online (Sandbox Code Playgroud)

主要问题是"命令"来自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;
}
Run Code Online (Sandbox Code Playgroud)

因此,如果(上图)我使用阻止.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.

Iva*_*oev 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;
}  
Run Code Online (Sandbox Code Playgroud)

编辑
虽然上面应该可行,但应该有更好的整体设计。不幸的是,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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)