使用动态类型的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
只有Id
和Content
。继承类可能有其他属性,但我只对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扩展框架
如果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);
}
}
}