如何以正确的方式取消异步查询

Posted

技术标签:

【中文标题】如何以正确的方式取消异步查询【英文标题】:How to cancel an async query the right way 【发布时间】:2013-08-07 09:57:55 【问题描述】:

这是this question 的后续问题。

我正在尝试从我的数据库中加载数据,这需要 5-10 秒,但我希望 GUI 保持响应,并且它应该可以取消。

private CancellationTokenSource _source;

public IEnumerable<Measurement> Measurements  get  ...  set  ...  

private async void LoadData()

    _source = new CancellationTokenSource();

    using (var context = new TraceContext())
    
        Measurements = null;
        Measurements = await context.Measurements.ToListAsync(_source.Token);
    


private void Cancel()

    if (_source != null)
        _source.Cancel();


public RelayCommand ReloadCommand

    get  return _reloadCommand ?? (_reloadCommand = new RelayCommand(Reload)); 

private RelayCommand _reloadCommand;

public RelayCommand CancelCommand

    get  return _cancelCommand ?? (_cancelCommand = new RelayCommand(Cancel)); 

private RelayCommand _cancelCommand;

我已经尝试了一些事情,但我无法让它正常工作,这只是加载列表,仅此而已,我无法取消它。

这其中的错误在哪里?

【问题讨论】:

当您说“我无法取消”时,当您告诉CancellationTokenSource 取消时,实际会发生什么? 什么都没有发生,据我所知,如果仍有使用此令牌运行的任务,它应该抛出异常,但这不会发生。 什么是ToListAsync?通过一些谷歌搜索,我找不到它作为 MSDN 或其他东西的一部分。也许你写了它并且它包含一个错误,所以令牌没有被正确应用? 这不是取消的工作方式。仅当尝试对任务进行Wait() 或观察任务的结果时,已取消的任务(已转换为Cancelled 状态的Task)引发异常。在这种情况下,任务由 EF 控制,并且只有在 EF 选择时才会转换到该状态。在不知道他们的内部细节的情况下,测试版可能不会这样做。 @TimS。这是新的 EF6 功能的一部分 msdn.microsoft.com/en-us/data/jj819165.aspx 编辑:goo.gl/kMR5D 可能是更好的来源 【参考方案1】:

感谢您提出这个问题。目前,EF 中此异步 API 的实现依赖于底层 ADO.NET 提供程序来支持取消,但 SqlDataReader.ReadAsync 有一些限制,我们观察到在许多情况下,当请求取消时它不会立即取消。我们正在考虑在 EF6 RTM 中修复 a bug,这是关于引入我们自己的检查,以检查 EF 方法内的行读取之间的取消请求。

与此同时,您可以通过使用 ForEachAsync() 将项目添加到列表并检查每一行来解决此限制,例如(未经彻底测试):

    public async static Task<List<T>> MyToListAsync<T>(
        this IQueryable<T> source,
        CancellationToken token)
    
        token.ThrowIfCancellationRequested();
        var list = new List<T>();
        await source.ForEachAsync(item =>
        
            list.Add(item);
            token.ThrowIfCancellationRequested();
        );
        return list;
    

【讨论】:

我现在已经尝试过了,它可以工作,但只有在加载数据之后(用wireshark检查所有数据都加载了,但列表的填充被取消了)。另外我认为ForEachAsync 也应该用CancellationToken => source.ForEachAsync(..., token) 调用,但它看起来像ForEachAsync 也没有以正确的方式实现取消模式,因为数据仍在加载并且它不会' t 抛出异常。 我提到的错误是关于在 ForEachAsync 中添加取消请求检查,因此当我们修复错误时应该解决这部分问题。另一个有趣的方面是,由于在 EF6 中我们默认缓冲查询结果,数据读取器的加载(即对 ReadAsyc 的调用)在我们开始具体化结果之前发生在下一级。我会将这个细节添加到错误中,以便我们在修复错误时考虑它。 顺便说一下,为了避免加载完整的结果,您可以将 .AsStreaming() 添加到查询中。这将产生其他后果,例如流式查询与连接弹性功能不兼容。

以上是关于如何以正确的方式取消异步查询的主要内容,如果未能解决你的问题,请参考以下文章

如何从客户端取消异步任务

如何删除/更新异步方式以及何时取消订阅?

前端热门技术axios详细讲解——取消异步请求

将取消令牌传递给异步流的正确方法是啥?

这个用于减少异步迭代的 Promise 取消的实现是否正确?

如何在 C# Winforms 应用程序中取消执行长时间运行的异步任务