web-dev-qa-db-fra.com

UOW - Une seconde opération a démarré sur ce contexte avant la fin d'une précédente opération asynchrone

J'essaie de suivre le code, il comporte deux parties, l'une est la navigation via un prisme . Lorsque la navigation est autorisée, je commence une charge profonde de manière asynchrone mais à chaque fois avec un nouveau contexte. Dans le code ultérieur, je souhaiterais annuler les navigations en attente qui ne sont pas terminées par ce chargement, mais le code ci-dessous ne fonctionne même pas. L'annulation est une question ultérieure .-)

logique de navigation: pas de problème ici

public void OnNavigatedTo(NavigationContext navigationContext)
{
    int relatieId = (int)navigationContext.Parameters["RelatieId"];
    if (_relatie != null && _relatie.RelatieId == relatieId) return;

    loadRelatieAsync(relatieId);
}

public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
    bool navigationAllowed = true;
    continuationCallback(navigationAllowed);
}

logique de chargement en profondeur:

private async Task loadRelatieAsync(int relatieId)
{
    try
    {
        await Task.Run(async () =>
        {

            _unitOfWork = _UnitOfWorkFactory.createUnitOfWorkAsync();

            IEnumerable<Relatie> relaties = await getRelatieAsync(_unitOfWork, relatieId).ConfigureAwait(true);

            _relatieTypeTypes = await getRelatieTypeTypesAsync(_unitOfWork, relatieId).ConfigureAwait(true);
            _relatie = relaties.FirstOrDefault();

            _unitOfWork.Dispose();
        }).ConfigureAwait(true);

        processRelatie(_relatie);

        processRelatieTypes(_relatie, _relatieTypeTypes);
    }
    catch (Exception Ex)
    {

        MessageBox.Show(Ex.Message);
        throw;
    }

}

private async Task<IEnumerable<Relatie>> getRelatieAsync(IUnitOfWorkAsync unitOfWork, int relatieId)
{

    IEnumerable<Relatie> relaties = null;
    try
    {
        IRepositoryAsync<Relatie> relatieRepository = unitOfWork.RepositoryAsync<Relatie>();
        relaties = await relatieRepository
            .Query(r => r.RelatieId == relatieId)
            .Include(i => i.BegrafenisOndernemer)
            .SelectAsync()
            .ConfigureAwait(false);

        IRepositoryAsync<Adres> adresRepository = unitOfWork.RepositoryAsync<Adres>();
        //exception is thrown after executing following line
        var adressen = await adresRepository
            .Query(r => r.RelatieId == relatieId)
            .Include(i => i.AdresType)
            .SelectAsync()
            .ConfigureAwait(false);
        _relatieTypeRepository = unitOfWork.RepositoryAsync<RelatieType>();
        var relatieTypes = await _relatieTypeRepository
            .Query(r => r.RelatieId == relatieId)
            .SelectAsync()
            .ConfigureAwait(false);
    }
    catch (Exception Ex)
    {
        MessageBox.Show(Ex.Message);//exception is shown here
        throw;
    }
    return relaties;
}

private async Task<IEnumerable<RelatieTypeType>> getRelatieTypeTypesAsync(IUnitOfWorkAsync unitOfWork, int relatieId)
{

    IEnumerable<RelatieTypeType> relatieTypeTypes = null;
    try
    {
        IRepositoryAsync<RelatieTypeType> relatieTypeTypeRepository =
            unitOfWork.RepositoryAsync<RelatieTypeType>();

        relatieTypeTypes = await relatieTypeTypeRepository
            .Query()
            .SelectAsync()
            .ConfigureAwait(false);

    }
    catch (Exception Ex)
    {
        MessageBox.Show(Ex.Message);
        throw;
    }
    return relatieTypeTypes;
}

Je continue à recevoir des exceptions comme si j'avais oublié d'attendre, mais ce n'est jamais le cas . J'utilise aussi correctement configureawait (true) chaque fois que je veux continuer sur le fil de l'interface graphique. Mais je continue à avoir cette erreur dans la logique de dé-téléchargement. L'unité de travail et les classes de référentiel utilisent également le mécanisme d'attente asynchrone, mais j'attends là aussi correctement.

Une deuxième opération a démarré dans ce contexte avant la fin d'une précédente opération asynchrone. Utilisez 'wait' pour vous assurer que toutes les opérations asynchrones sont terminées avant d'appeler une autre méthode sur ce contexte. Il n'est pas garanti que les membres d'instance soient thread-safe.

Edit (code de l'enregistreur supprimé pour réduire la taille du code) 

12
Philip Stuyck

Le problème est ce code:

_unitOfWork = _UnitOfWorkFactory.createUnitOfWorkAsync();

IEnumerable<Relatie> relaties = await getRelatieAsync(_unitOfWork, relatieId).ConfigureAwait(true);

_relatieTypeTypes = await getRelatieTypeTypesAsync(_unitOfWork, relatieId).ConfigureAwait(true);
_relatie = relaties.FirstOrDefault();

 _unitOfWork.Dispose();

unitOfWork est une variable d'instance lorsque le scénario commence une seconde fois, il est remplacé par une nouvelle instance. Le scénario en cours d'exécution utilise ensuite ce nouveau UnitOfWork au lieu du sien, car il est écrasé. Pas si facile à repérer, mais une simple condition de concurrence . Je l'ai trouvée en remplaçant toutes les variables d'instance par des variables locales, puis le problème a disparu.

15
Philip Stuyck

Ce n'est probablement pas la solution, mais un aperçu général de votre code.

Le but principal de async/wait est de garder le thread actuel disponible. Cela aide à ne pas bloquer le thread d'interface utilisateur et à garder votre application réactive.

Vous vous assurez déjà que votre chargement en profondeur se passe sur un thread ThreadPool, car vous le démarrez entièrement en utilisant Task.Run () . Vous pouvez probablement résoudre la plupart de vos problèmes en utilisant les méthodes synchrones par défaut d'EntityFramework dans votre mécanisme de chargement.

Dans un premier temps, votre code a l'apparence fine dès les appels asynchrones. Peut-être que votre chargement en profondeur est déclenché plusieurs fois?

1
Kai Brummund