Async не работи с EF + единица работа + Repo

Опитах се да внедря модели на единица работа и репо заедно с Entity Framework и ASP.net API проект.

Първо, исках да отбележа, че знам, че DbContext & DbSets са имплементации на UoW & Repo модели, но си играя, за да видя какво ще пасне на моя проект.

ПРОБЛЕМ

Забелязах, че ако извикам async repo методи от моята услуга, нищо не се случва, методът се извиква, но изглежда, че изчаква никога не се задейства. Ако извикам метода синхронно, всичко е наред. (пример за метод е Count / CountAsync).

Още по-странното (и не мога да разбера защо) е, че същото извикване на метод по някаква причина работи в един метод на услугата, но не и в друг.

Ще обясня по-подробно, след като представя моя код.

СТРУКТУРА НА ПРОЕКТА

Моят проект е структуриран по следния начин:

  • Имам API, в който се инжектира слоя на услугата
  • In-Service са инжектирани UoW & Repos
  • В Repo се инжектира UoW
  • Накрая UoW извиква Database Context Factory, чието задължение е да създаде ново копие на моя DbContex

--- КОД ---

Ето текущата реализация, разбира се, някои части от кода бяха пропуснати за краткост.

Фабрика за контекст на база данни

/// <summary>
///     Interface for factory which is in charge of creating new DbContexts
/// </summary>
/// <autogeneratedoc />
public interface IDatabaseContextFactory
{
    /// <summary>
    /// Creates new Master Database Context.
    /// </summary>
    /// <returns>newly created MasterDatabaseContext</returns>
    /// <autogeneratedoc />
    DbContext MasterDbContext();
}


/// <inheritdoc />
/// <summary>
/// This is factory which is in charge of creating new DbContexts
/// It is implemented as Singleton as factory should be implemented (according to Gang of four) 
/// </summary>
/// <seealso cref="T:Master.Domain.DataAccessLayer.IDatabaseContextFactory" />
/// <autogeneratedoc />
public class DatabaseContextFactory : IDatabaseContextFactory
{
    /// <summary>
    /// This is implementation of singleton
    /// </summary>
    /// <remarks>
    /// To read more, visit: http://csharpindepth.com/Articles/General/Singleton.aspx (Jon skeet)
    /// </remarks>
    /// <autogeneratedoc />
    private static readonly DatabaseContextFactory instance = new DatabaseContextFactory();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit (more about this: http://csharpindepth.com/Articles/General/Beforefieldinit.aspx)
    static DatabaseContextFactory()
    {
        
    }

    //so that class cannot be initiated 
    private DatabaseContextFactory()
    {
    }


    /// <summary>
    /// Instance of DatabaseContextFactory
    /// </summary>
    public static DatabaseContextFactory Instance => instance;

    /// <inheritdoc />
    /// <summary>
    /// Creates new MasterDatabaseContext
    /// </summary>
    /// <returns></returns>
    public DbContext MasterDbContext()
    {
        return new MasterDatabaseContext();
    }
}

Работна единица

 /// <inheritdoc />
/// <summary>
/// Unit of work interface
/// Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
/// </summary>
/// <seealso cref="T:System.IDisposable" />
/// <autogeneratedoc />
public interface IUnitOfWork : IDisposable
{
    /// <summary>
    /// Gets the database context. DatabaseContext is part of EF and itself is implementation of UoW (and repo) patterns
    /// </summary>
    /// <value>
    /// The database context.
    /// </value>
    /// <remarks> 
    /// If true  UoW was implemented this wouldn't be here, but we are exposing this for simplicity sake. 
    /// For example so that repository  could use benefits of DbContext and DbSet <see cref="DbSet"/>. One of those benefits are Find and FindAsnyc methods
    /// </remarks>
    /// <autogeneratedoc />
    DbContext DatabaseContext { get; }
    /// <summary>
    /// Commits the changes to database
    /// </summary>
    /// <returns></returns>
    /// <autogeneratedoc />
    void Commit();
    
    /// <summary>
    /// Asynchronously commits changes to database.
    /// </summary>
    /// <returns></returns>
    /// <autogeneratedoc />
    Task CommitAsync();
 
}

 /// <inheritdoc />
/// <summary>
/// This is implementation of UoW pattern
/// </summary>
/// <remarks>
/// Martin Fowler: "Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems."
/// According to P of EEA, Unit of work should have following methods: commit(), registerNew((object), registerDirty(object), registerClean(object), registerDeleted(object)
/// The thing is DbContext is already implementation of UoW so there is no need to implement all this
/// In case that we were not using ORM all these methods would have been implemented
/// </remarks>
/// <seealso cref="T:Master.Domain.DataAccessLayer.UnitOfWork.IUnitOfWork" />
/// <autogeneratedoc />
public class UnitOfWork : IUnitOfWork
{
    /// <summary>
    /// Is instance already disposed
    /// </summary>
    /// <remarks>
    /// Default value of bool is false
    /// </remarks>
    /// <autogeneratedoc />
    private bool _disposed;

    /// <summary>
    /// Initializes a new instance of the <see cref="UnitOfWork"/> class.
    /// </summary>
    /// <param name="dbContextfactory">The database context factory.</param>
    /// <exception cref="ArgumentNullException">
    /// dbContextfactory
    /// or
    /// MasterDbContext - Master database context cannot be null
    /// </exception>
    /// <autogeneratedoc />
    public UnitOfWork(IDatabaseContextFactory dbContextfactory)
    {
        if (dbContextfactory == null)
        {
            throw new ArgumentNullException(nameof(dbContextfactory));
        }

        var MasterDbContext = dbContextfactory.MasterDbContext();

        if (MasterDbContext == null)
        {
            throw new ArgumentNullException(nameof(MasterDbContext), @"Master database context cannot be null");
        }

        DatabaseContext = MasterDbContext;
    }

    /// <summary>
    /// Gets the database context. DatabaseContext is part of EF and itself is implementation of UoW (and repo) patterns
    /// </summary>
    /// <value>
    /// The database context.
    /// </value>
    /// <remarks>
    /// If true  UoW was implemented this wouldn't be here, but we are exposing this for simplicity sake.
    /// For example so that repository  could use benefits of DbContext and DbSet <see cref="DbSet" />. One of those benefits are Find and FindAsnyc methods
    /// </remarks>
    /// <autogeneratedoc />
    public DbContext DatabaseContext { get; }

    /// <inheritdoc />
    /// <summary>
    /// Commits the changes to database
    /// </summary>
    /// <autogeneratedoc />
    public void Commit()
    {
         DatabaseContext.SaveChanges();
    }

    /// <inheritdoc />
    /// <summary>
    /// Asynchronously commits changes to database.
    /// </summary>
    /// <returns></returns>
    /// <autogeneratedoc />
    public async Task CommitAsync()
    {
        await DatabaseContext.SaveChangesAsync();
    }


    /// <inheritdoc />
    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    /// <autogeneratedoc />
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Releases unmanaged and - optionally - managed resources.
    /// </summary>
    /// <param name="disposning"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
    /// <autogeneratedoc />
    protected virtual void Dispose(bool disposning)
    {
        if (_disposed)
            return;


        if (disposning)
        {
            DatabaseContext.Dispose();
        }


        _disposed = true;
    }

    /// <summary>
    /// Finalizes an instance of the <see cref="UnitOfWork"/> class.
    /// </summary>
    /// <autogeneratedoc />
    ~UnitOfWork()
    {
        Dispose(false);
    }
}

Общо хранилище

/// <summary>
/// Generic repository pattern implementation
/// Repository  Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
/// </summary>
/// <remarks>
/// More info: https://martinfowler.com/eaaCatalog/repository.html
/// </remarks>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <autogeneratedoc />
public interface IMasterRepository<TEntity, in TKey> where TEntity : class
{
    /// <summary>
    ///     Gets entity (of type) from repository based on given ID
    /// </summary>
    /// <param name="id">The identifier.</param>
    /// <returns>Entity</returns>
    /// <autogeneratedoc />
    TEntity Get(TKey id);

    /// <summary>
    /// Asynchronously gets entity (of type) from repository based on given ID
    /// </summary>
    /// <param name="id">The identifier.</param>
    /// <returns></returns>
    /// <autogeneratedoc />
    Task<TEntity> GetAsnyc(TKey id);
    
    /// <summary>
    ///     Gets all entities of type from repository
    /// </summary>
    /// <returns></returns>
    /// <autogeneratedoc />
    IEnumerable<TEntity> GetAll();

    /// <summary>
    ///  Asynchronously gets all entities of type from repository
    /// </summary>
    /// <returns></returns>
    /// <autogeneratedoc />
    Task<IEnumerable<TEntity>> GetAllAsync();

    /// <summary>
    ///     Finds all entities of type which match given predicate
    /// </summary>
    /// <param name="predicate">The predicate.</param>
    /// <returns>Entities which satisfy conditions</returns>
    /// <autogeneratedoc />
    IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);
}


//Note to self: according to P of EAA Repo plays nicely with QueryObject, Data mapper and Metadata mapper - Learn about those !!!



/// <summary>
/// Generic repository pattern implementation
/// Repository  Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <seealso cref="Master.Domain.DataAccessLayer.Repository.Generic.IMasterRepository{TEntity, TKey}" />
/// <inheritdoc cref="IMasterRepository{TEntity,TKey}" />
public class MasterRepository<TEntity, TKey> : IMasterRepository<TEntity, TKey>
    where TEntity : class 
{

    /// <summary>
    /// DbSet is part of EF, it holds entities of the context in memory, per EF guidelines DbSet was used instead of IDbSet 
    /// </summary>
    /// <remarks>
    /// <para>
    /// Even though we are not 100% happy about this, 
    /// We decided to go with this instead of (for example) IEnumerable so that we can use benefits of <see cref="DbSet"/>
    /// Those benefits for example are Find and FindAsync methods which are much faster in fetching entities by their key than for example Single of First methods
    /// </para>
    /// </remarks>
    /// <autogeneratedoc />
    private readonly DbSet<TEntity> _dbSet;


    /// <summary>
    /// Initializes a new instance of the <see cref="MasterRepository{TEntity, TKey}"/> class.
    /// </summary>
    /// <param name="unitOfWork">The unit of work.</param>
    /// <exception cref="ArgumentNullException">unitOfWork - Unit of work cannot be null</exception>
    /// <autogeneratedoc />
    public MasterRepository(IUnitOfWork unitOfWork)
    {
        if (unitOfWork == null)
        {
            throw new ArgumentNullException(nameof(unitOfWork), @"Unit of work cannot be null");
        }

        _dbSet = unitOfWork.DatabaseContext.Set<TEntity>();
    }

    /// <inheritdoc />
    /// <summary>
    /// Gets entity with given key
    /// </summary>
    /// <param name="id">The key of the entity</param>
    /// <returns>Entity with key id</returns>
    public TEntity Get(TKey id)
    {
        return _dbSet.Find(id);
    }

    /// <inheritdoc />
    /// <summary>
    /// Asynchronously gets entity with given key
    /// </summary>
    /// <param name="id">The key of the entity</param>
    /// <returns>Entity with key id</returns>
    public async Task<TEntity> GetAsnyc(TKey id)
    {
         return await _dbSet.FindAsync(id);
    }

    /// <inheritdoc />
    /// <summary>
    /// Gets all entities
    /// </summary>
    /// <returns>List of entities of type TEntiy</returns>
    public IEnumerable<TEntity> GetAll()
    {
        return _dbSet.ToList();
    }

    public async Task<IEnumerable<TEntity>> GetAllAsync()
    {
        return await _dbSet.ToListAsync();

    }

    public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.Where(predicate).ToList();
    }

}

Хранилище за запазени филми

 /// <inheritdoc />
/// <summary>
/// Repository for dealing with <see cref="T:Master.Domain.Model.MovieAggregate.SavedMovie" /> entity
/// </summary>
/// <seealso cref="!:Master.Domain.DataAccessLayer.Repository.Generic.IMasterRepository{Master.Domain.Model.MovieAggregate.SavedMovie,System.Guid}" />
/// <autogeneratedoc />
public interface ISavedMoviesRepository : IMasterRepository<SavedMovie, Guid>
{
    /// <summary>
    /// Asynchronously Gets number of saved Movies for the user
    /// </summary>
    /// <param name="user">The user.</param>
    /// <returns>Number of saved Movies</returns>
    /// <autogeneratedoc />
    Task<int> CountForUser(Model.UserAggregate.User user);
}


/// <inheritdoc cref="ISavedMoviesRepository" />
/// />
/// <summary>
///     Repository for dealing with <see cref="T:Master.Domain.Model.MovieAggregate.SavedMovie" /> entity
/// </summary>
/// <seealso cref="!:Master.Domain.DataAccessLayer.Repository.Generic.MasterRepository{Master.Domain.Model.MovieAggregate.SavedMovie, System.Guid}" />
/// <seealso cref="T:Master.Domain.DataAccessLayer.Repository.SavedMovies.ISavedMoviesRepository" />
/// <autogeneratedoc />
public class SavedMovieRepository : MasterRepository<SavedMovie, Guid>, ISavedMoviesRepository
{
    /// <summary>
    ///     Ef's DbSet - in-memory collection for dealing with entities
    /// </summary>
    /// <autogeneratedoc />
    private readonly DbSet<SavedMovie> _dbSet;
    private readonly IUnitOfWork _unitOfWork;



    /// <inheritdoc />
    /// <summary>
    ///     Initializes a new instance of the
    ///     <see cref="T:Master.Domain.DataAccessLayer.Repository.SavedMovies.SavedMovieRepository" /> class.
    /// </summary>
    /// <param name="unitOfWork">The unit of work.</param>
    /// <exception cref="T:System.ArgumentNullException"></exception>
    /// <autogeneratedoc />
    public SavedMovieRepository(UnitOfWork.UnitOfWork unitOfWork) : base(unitOfWork)
    {
        if (unitOfWork == null)
            throw new ArgumentNullException();
        _dbSet = unitOfWork.DatabaseContext.Set<SavedMovie>();
        _unitOfWork = unitOfWork;

    }

    /// <inheritdoc />
    /// <summary>
    ///     Asynchronously Gets number of saved Movies for the user
    /// </summary>
    /// <param name="user">The user.</param>
    /// <returns>
    ///     Number of saved Movies
    /// </returns>
    /// <exception cref="T:System.ArgumentNullException">user - User cannot be null</exception>
    /// <autogeneratedoc />
    public async Task<int> CountForUser(Model.UserAggregate.User user)
    {
        if (user == null)
            throw new ArgumentNullException(nameof(user), @"User cannot be null");

        return await _dbSet.CountAsync(r => r.UserWhoSavedId == user.Id);
    }
}

Услуга за запазени филми

/// <inheritdoc />
/// <summary>
///     This is service for handling saved Movies!
/// </summary>
/// <seealso cref="T:Master.Infrastructure.Services.SavedMovieService.Interfaces.ISavedMovieService" />
/// <autogeneratedoc />
public class SavedMovieService : ISavedMovieService
{
    /// <summary>
    /// The saved Movies repository <see cref="ISavedMoviesRepository"/>
    /// </summary>
    /// <autogeneratedoc />
    private readonly ISavedMoviesRepository _savedMoviesRepository;
   
    /// <summary>
    /// The unit of work <see cref="IUnitOfWork"/>
    /// </summary>
    /// <autogeneratedoc />
    private readonly IUnitOfWork _unitOfWork;

    /// <summary>
    /// The user repository <see cref="IUserRepository"/>
    /// </summary>
    /// <autogeneratedoc />
    private readonly IUserRepository _userRepository;

    /// <summary>
    /// Initializes a new instance of the <see cref="SavedMovieService"/> class.
    /// </summary>
    /// <param name="savedMoviesRepository">The saved Movies repository.</param>
    /// <param name="userRepository">The user repository.</param>
    /// <param name="unitOfWork">The unit of work.</param>
    /// <autogeneratedoc />
    public SavedMovieService(ISavedMoviesRepository savedMoviesRepository, IUserRepository userRepository,
        IUnitOfWork unitOfWork)
    {
        _savedMoviesRepository = savedMoviesRepository;
        _userRepository = userRepository;
        _unitOfWork = unitOfWork;
    }

    public Task<int> CountNumberOfSavedMoviesForUser(string userId)
    {
        if (string.IsNullOrEmpty(userId))
            throw new ArgumentNullException(nameof(userId), @"User id must not be empty");


        var user = _userRepository.Get(userId);
        return _savedMoviesRepository.CountForUser(user);
    }
    
      public async Task<Guid> SaveWorkoutFromLibraryAsync(string userWhoIsSavingId, Guid galleryId,
        bool isUserPro)
    {
        if (string.IsNullOrEmpty(userWhoIsSavingId))
            throw new ArgumentNullException(nameof(userWhoIsSavingId), @"User id cannot be empty");

        if (galleryId == Guid.Empty)
            throw new ArgumentException(@"Id of gallery cannot be empty", nameof(galleryId));

            //get user who is saving from DB
            var userWhoIsSaving = _userRepository.Get(userWhoIsSavingId);
           

            if (userWhoIsSaving == null)
                throw new ObjectNotFoundException($"User with provided id not found - id: {userWhoIsSavingId}");

            //how many movies has this user saved so far
            var numberOfAlreadySavedMoviesForUser = await _savedWorkoutsRepository.CountForUserAsync(userWhoIsSaving);

            // more code here
    }

}

Уеб API контролер

[Authorize]
[RoutePrefix("api/Saved")]
[ApiVersion("2.0")]
public class SavedController : ApiController
{
    private readonly ISavedMovieService _savedMovieService;



    /// <inheritdoc />
    /// <summary>
    ///     Initializes a new instance of the <see cref="T:Master.Infrastructure.Api.V2.Controllers.SavedController" /> class.
    /// </summary>
    /// <param name="savedMovieService">The saved Movie service.</param>
    /// <autogeneratedoc />
    public SavedController(ISavedMovieService savedMovieService)
    {        
        _savedMovieService = savedMovieService;
    }

    public async Task<IHttpActionResult> GetNumberOfSavedForUser()
    {
        var cnt = await _savedMovieService.CountNumberOfSavedMoviesForUser(User.Identity.GetUserId());

        return Ok(cnt);
    }
    
    public async Task<IHttpActionResult> SaveFromGalery(SaveModel model)
    {
        await _savedMovieService.SaveWorkoutFromGalleryAsync(User.Identity.GetUserId(), model.Id, model.IsPro);

        return Ok();
    }
}

Конфигурация на Ninject

(Само важни части)

        kernel.Bind<MasterDatabaseContext>().ToSelf().InRequestScope();
        kernel.Bind<IDatabaseContextFactory>().ToMethod(c => DatabaseContextFactory.Instance).InSingletonScope();
        kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();

        kernel.Bind(typeof(IMasterRepository<,>)).To(typeof(MasterRepository<,>));

        kernel.Bind<ISavedMoviesRepository>().To<SavedMovieRepository>();
        kernel.Bind<IUserRepository>().To<UserRepository>();
        kernel.Bind<ISavedMovieService>().To<SavedMovieService>();

Исках да отбележа, че имам още няколко хранилища (общо 4, включително Saved и User), инжектирани в SavedService, но не вярвам, че са подходящи, тъй като са почти същите като SavedRepo, но ако е необходимо, Мога и да ги добавя. Също така, това е единствената услуга, която в момента прилага този модел и методи.

И така, ето какво се случва, когато се обадя на SaveFromGalery:

  1. Извиква се UoW конструктор
  2. Извиква се DatabaseContextFactory MasterDbContext().
  3. Извиква се конструктор MasterRepository
  4. Конструкторът SavedMoviesRepository се извиква
  5. Извиква се UoW конструктор (отново втори път)
  6. DatabaseContextFactory MasterDbContext() се извиква (втори път)
  7. Извиква се конструктор MasterRepository (отново втори път)
  8. Извиква се UserRepository
  9. Конструкторът на MasterRepository се извиква (отново 3-ти път)
  10. Извиква се конструктор MasterRepository (отново 4-ти път)
  11. Извиква се конструктор SavedService
  12. Извиква се HTTP GET SaveFromGalery
  13. потребителят е извлечен от потребителското репо успешно
  14. Извиква се _savedWorkoutsRepository.CountForUserAsync
  15. програмата въвежда метода, удря чакането, но никога не връща резултата

От друга страна, когато се извика GetNumberOfSavedForUser, ето какво се случва:

  1. 1 - 11 стъпки са еднакви
  2. Извиква се HTTP GET GetNumberOfSavedForUser
  3. потребителят е извлечен от потребителското репо успешно
  4. _savedWorkoutsRepository.CountForUserAsync се извиква УСПЕШНО
  5. Извиква се UoW Dispose
  6. Извиква се UoW dispose of

Освен това, както беше посочено по-рано, ако _savedWorkoutsRepository.CountForUserAsync е направен синхронен, всичко върви добре.

Може ли някой да ми помогне да разбера какво точно се случва?


person hyperN    schedule 15.09.2017    source източник
comment
Може би може да има задънени позиции. Опитайте се да използвате .ConfigureAwait(false) и лично аз бих преработил методи като async Task<T> SomeMethod(){ return await someService.MethodAsync();} до Task<T> SomeMethod(){ return someService.MethodAsync();}   -  person Fildor    schedule 15.09.2017
comment
@Fildor благодаря ви за отговора и съвета за рефакторинг. За съжаление .ConfigureAwait(false) не помогна :(   -  person hyperN    schedule 15.09.2017
comment
@Fildor Моя грешка! Всъщност помогна. Моля, приемете моите извинения, отговорих набързо, поставих .ConfigureAwait(false) както в репо, така и в услуга и това помогна. Знаете ли може би какво може да използва тази безизходица и как .ConfigureAwait(false) помогна? Благодаря!   -  person hyperN    schedule 15.09.2017
comment
msdn.microsoft.com/en-us/magazine/jj991977.aspx   -  person MickyD    schedule 15.09.2017
comment
@MickyD @ Fildor благодаря за коментарите ви, те ми помагат да разбера какво се случи. Случайно пропуснах await в моя API метод. Имам го тук като пример, но това е така, защото съм изчистил малко кода за въпроса, така че да е по-лесно за четене и подсъзнателно го добавих тук и той все още липсваше в моя истински код, колко глупаво от моя страна. Благодаря, че ми помогнахте да осъзная тази грешка!   -  person hyperN    schedule 15.09.2017
comment
Вижте също scalablenotions.wordpress.com/2015/05/02/ Точка 3.   -  person Fildor    schedule 15.09.2017
comment
@Fildor страхотна статия, благодаря!   -  person hyperN    schedule 15.09.2017


Отговори (1)


Вероятно използвате Wait или Result във вашия код от реалния свят (не кода, публикуван тук, който е непълен). Това ще причини блокиране на ASP. NET класика.

По-конкретно, това, което се случва, е, че когато подадете задача към await, по подразбиране тя ще улови „текущия контекст“ и ще го използва, за да възобнови асинхронния метод, когато тази задача завърши. След това кодът блокира задача (т.е. Wait или Result). Проблемът е, че контекстът на ASP.NET classic позволява само една нишка наведнъж. Така че докато тази нишка е блокирана за задачата, тя "заема" този контекст, което всъщност пречи на задачата да бъде завършена. Следователно безизходица.

Имайте предвид, че ConfigureAwait(false) не е поправка; в най-добрия случай е заобиколно решение. Правилното решение е да замените Wait / Result с await.

person Stephen Cleary    schedule 15.09.2017
comment
Благодаря ви за отговора, прав сте, всъщност напълно забравих да напиша ключовата дума await в моя API метод, тъй като бях доста уморен :/ Търсих навсякъде в кода, за да намеря виновника, но не забелязах, че това е глупава грешка. Благодаря! - person hyperN; 15.09.2017
comment
Като странична бележка, уверете се, че обръщате внимание на предупрежденията на компилатора. Visual Studio е доста добър в намирането на липсващи awaits - не може винаги да ги намери, но го прави през повечето време. - person Stephen Cleary; 15.09.2017
comment
Благодаря ви за този съвет, не знаех за това. Всъщност имам Reshaper, който също е добър в намирането на неща като това, но не ме предупреди. подозирам, защото в действителния код цялото нещо беше увито в Ok(); отговор - person hyperN; 15.09.2017