UOW - 在上一个异步操作完成之前,此上下文中已启动了第二个操作

13

我正在尝试以下代码,它有两个部分,一个是通过棱镜进行导航。 当允许导航时,我会异步地开始深度加载,但每次都使用新的上下文。在以后的代码中,我想取消未完成此加载的待定导航,但下面的代码甚至不起作用,所以取消暂时是个问题;)

导航逻辑:这里没有问题

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

深度加载逻辑:

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

我一直收到异常,就好像我忘记了使用await一样,但事实并非如此。每当我想要在GUI线程上继续执行时,我也会正确地使用configureawait(true)。但我在deeploading逻辑中不断收到这个错误。UnitOfWork和Repository类也使用async await机制,但在那里我也正确使用了await。

在先前的异步操作完成之前,该上下文上已启动第二个操作。使用'await'确保在调用该上下文上的另一个方法之前完成任何异步操作。任何实例成员都不能保证是线程安全的。

编辑(为减少代码大小已删除记录器代码)


请注意,createUnitOfWorkAsync()不是异步方法。它只是创建一个支持async await范式的类。 - Philip Stuyck
1
createUnitOfWorkAsync会创建一个新的上下文,对吗? - Stephen Cleary
1
ConfigureAwait(true); 这是第一次。 - Yuval Itzchakov
那么对于 Stack Overflow,我建议您剥离掉一些不必要的代码,以便人们更容易地找到与您有关的代码部分,并帮助您解决问题(例如,在所有这些日志记录中很难找到您的问题)。 - Kai Brummund
Downvoting 是用于当问题没有展示出研究努力的情况下。显然,如果你看一下最终答案,会发现有一个不太容易发现的 bug。我敢打赌那个点踩的人没有看到它。 - Philip Stuyck
显示剩余4条评论
2个回答

16

问题出在这段代码:

_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是一个实例变量,当场景第二次启动时,它会被新实例覆盖。正在执行的场景随后使用该新的UnitOfWork而不是它自己的,因为它已经被覆盖了。 虽然不容易察觉,但这是一个简单的竞态条件。 我通过将所有的实例变量替换为本地变量来发现它,然后问题就消失了。


当你说“通过用局部变量替换实例变量来修复这个问题”时,你是指在方法内的变量,并将它们替换为在实例类中创建的字段变量吗?只是出于好奇想了解一下。 - Daniel Jackson
2
我的意思是相反的,局部变量的范围仅限于声明它的方法,而实例变量的范围则与它所包含的对象实例一样长。 - Philip Stuyck
好的,我明白你的意思。我还在学习中。我认为我需要更多关于使用async/await编程的理解,因为我现在是不完全了解它就在使用,所以遇到了与上述相同的问题。当我改回直接同步时,我就不再有这些问题了。 - Daniel Jackson
你救了我的命!! - Gajendra

1

这可能不是答案,只是对你的代码进行了一般性的查看。

async/await 的主要目的是保持当前线程可用。这有助于避免阻塞 UI 线程并使应用程序保持响应。

您已经确保深度加载在 ThreadPool 线程上进行,因为您完全使用 Task.Run() 启动它。您可以通过在加载机制中使用 EntityFramework 的默认同步方法来解决大部分问题。

乍一看,你的代码在 async 调用方面看起来不错。也许您的深度加载被多次触发了?


谢谢您的评论,我这样做是因为我想在下一个增量中使用取消令牌来取消操作。 方法loadRelatieAsync被多次调用并且正在并发运行,因为还没有取消,但这不应该有影响,因为每次都使用不同的上下文。它是在unitofwork创建之下创建的。 您是正确的,我正在尽可能地解除GUI线程的阻塞,但我也希望能够取消挂起的导航操作。 - Philip Stuyck
你仍然只能在操作之间检查取消请求,那时不会有任何区别。 - Kai Brummund
为什么不检查一下是否已经开始了加载操作,并返回其任务,如果已经存在的话?另外,您对上下文是否百分之百确定?因为当正在进行的事务导致错误时,那很可能就是问题所在。 - Kai Brummund

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接