Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inconsistent behavior when using await with dynamic type

I'm trying to use dynamic to go around inconveniences caused by design or lack of it (the "inconvenience" can be found here, if interested Simplify method retrieving data from generic repository).

To make it short, I need to return collection of Entity instances. Class is pretty simple:

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

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

So Entity has only Id and Content. Inheriting classes might have other properties but I'm only interested about the Content part (complex JSON).

All kinds of different entities can be accessed via generic Repository<T>. I need to know the Type of concrete class because T maps to underlying SQLite tables via data provider, built on top of SQLite-net ORM.

So, for example, if I have Schedule : Entity, then I'd be using Repository<Schedule> to manipulate table named Schedule. This part works just fine.

// 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.
}

Main problem is that "commands" are coming from JavaScript client so I'll receive requests in JSON format. In this JSON I have a property called CollectionName which specifies the desired table (and the concrete type).

What I need/want is a nice & clean piece of code that can fetch entities from any given table. So, method below was supposed to solve all my problems, but turns out it didn't...

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

So if (above) I use blocking .Result everything works liek a charm. Instead, if I use await, code might or might not work. It really seems to depend on positions of planets and/or mood swings of Flying Spaghetti Monster.

Randomly, but more often than not, given line will be throwing

Unable to cast object of type 'System.Runtime.CompilerServices.TaskAwaiter'1[System.Collections.Generic.IEnumerable'1[MyNamespace.Schedule]]' to type 'System.Runtime.CompilerServices.INotifyCompletion'.

I'm using .NET 4.0 Extended Framework.

like image 435
Mikko Viitala Avatar asked Aug 18 '15 19:08

Mikko Viitala


People also ask

What happens when async method is not awaited?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

Does await pause the thread?

Asynchronous MethodsWhen the executing thread reaches an await expression, it hits the “pause” button and the method execution is suspended. When the task being awaited completes, it hits the “play” button, and the method execution is resumed.

Why is asynchronous slow?

Async improves performance because it frees up a thread to keep on doing work while it is waiting for a high-latency result to appear. If there is no high-latency result then async will just slow everything down.

Is await async the same as sync?

The differences between asynchronous and synchronous include: Async is multi-thread, which means operations or programs can run in parallel. Sync is single-thread, so only one operation or program will run at a time. Async is non-blocking, which means it will send multiple requests to a server.


2 Answers

If the Repository<T> type is a type of your own making, you can have it be based on an abstract base type that has an abstract Task<IEnumerable<Entity>> GetAllAsync(). Then, since your Repository apparently already has a method of that signature - so you're good:

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

Then have your Repository<Entity> be based on Repository.

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

Then, when using it, instead of dynamic, you can say:

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
}

No dynamics at all.

like image 161
Clay Avatar answered Sep 28 '22 10:09

Clay


It can be achieved with 2 dynamic calls:

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

Edit
Although the above should work, there should be a better overall design. Unfortunately MS decided to use tasks for asynchrony, and since Task<TResult> is class, we cannot take benefit from covariance. However, we can do that with the help of a little generic extension with the cost of a little GC garbage. But IMO it greatly simplifies such designs/implementations. Check it out:

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);
        }
    }
}
like image 29
Ivan Stoev Avatar answered Sep 28 '22 08:09

Ivan Stoev