【问题标题】:Cancel Async operation取消异步操作
【发布时间】:2017-09-08 12:13:28
【问题描述】:
private async void TriggerWeekChanged(Week currentWeek)
{
    await LoadDataForSelectedWeek(currentWeek); //Split into multiple methods 
}

如果用户敲击Change_Week 按钮,我如何取消当前任务,并使用新参数启动新任务?

我试过这样:

private async Task Refresh(Week selectedWeek, CancellationToken token)
{
    Collection.Clear();

    await LoadDataFromDatabase();

    token.ThrowIfCancellationRequested();

    await ApplyDataToBindings();

    token.ThrowIfCancellationRequested();

    //Do some other stuff
}

问题是: 在我的Collection 中,当我连续点击快速按钮时,我获得了数周的数据。

【问题讨论】:

  • 您应该粘贴 LoadDataForSelectedWeek 和取消代码整个方法。
  • 我刚刚注意到您的TriggerWeekChanged 方法是async void。请查看我在答案末尾添加的注释。
  • @BradleyUffner 这是我在创建问题时犯的一个错误。在我的代码中,它的类型是 Task。感谢提及!

标签: c# wpf multithreading cancellationtokensource


【解决方案1】:

所有等待的东西都需要在 CancellationToken 上可见。如果它是您编写的自定义Task,它应该接受它作为参数,并在函数内定期检查它是否已被取消。如果是这样,它应该采取任何必要的行动(如果有的话)来停止或回滚正在进行的操作,然后调用ThrowIfCancellationRequested()

在您的示例代码中,您可能希望将token 传递给LoadDataFromDatabaseApplyDataToBindings,以及这些任务的任何子任务。

可能有一些更高级的情况,您不想将 same CancellationToken 传递给子任务,但您仍然希望它们可以取消。在这些情况下,您应该在 TaskCancellationtoken 的内部创建一个新的,用于子任务。

要记住的重要一点是ThrowIfCancellationRequested 标记了Task 内的安全位置,Task 可以被停止。没有保证运行时自动检测安全位置的安全方法。如果Task 在请求取消后立即自动取消其自身,则它可能会处于未知状态,因此由开发人员标记这些安全位置。在您的 Task 中分散多次调用检查取消的情况并不少见。


我刚刚注意到您的TriggerWeekChanged 函数是async void。当它用于不是事件处理程序的东西时,这通常被认为是一种反模式。在跟踪方法中async 操作的完成状态以及处理可能从其中抛出的任何异常时,它可能会导致很多问题。您应该非常厌倦任何标记为async void 而不是事件处理程序的东西,因为在 99% 或更多的情况下,这样做是错误的。我强烈建议将其更改为 async Task,并考虑从您的其他代码中传入 CancellationToken

【讨论】:

    【解决方案2】:

    你没有提到LoadDataForSelectedWeekRefresh 是如何交互的。

    简而言之,您需要创建一个处理每次点击的CancellationTokenSource 实例。然后将其传递给该方法并在每次出现新方法时执行Cancel 方法。

    private async void TriggerWeekChanged(Week currentWeek, CancellationTokenSource tokenSource)
    {
        tokenSource.Cancel();
        try
        {
            var loadDataTask = Task.Run(() => LoadDataForSelectedWeek(currentWeek, tokenSource.Token), tokenSource.Token); //Split into multiple methods
        }
        catch(OperationCanceledException ex)
        {
            //Cancelled
        }
    }
    

    LoadDataForSelectedWeek -> 刷新 (?)

    private async Task Refresh(Week selectedWeek, CancellationToken token)
    {
        Collection.Clear();
    
        await LoadDataFromDatabase();
    
        token.ThrowIfCancellationRequested();
    
        await ApplyDataToBindings();
    
        token.ThrowIfCancellationRequested();
    
        //Do some other stuff
    }
    

    【讨论】:

    • Task.WaitAll 不是一个选项,因为它完全阻塞了我的 UI 线程......这就是我使用 async/await 的原因
    • 然后我会为全球Thread 拍摄并将TriggerWeekChanged 中的所有内容发送到那里。或者,如果您不需要 //Refresh Passed 中的代码,并且您完全了解将这些方法发送给其他未等待任务的后果,那么您可以简单地删除 Task.WaitAll
    • @Erexo 不需要这样的回归。您可以使用await Task.WhenAll()。此外,asynchronous 并不意味着涉及后台线程。真正的异步 IO 操作不使用线程。
    • 事实上,Task.WaitAll 只会扼杀问题的主要目的。执行时,由于Task.WaitAll,由于 UI 线程阻塞,玩家无法再次点击按钮,因此无法杀死任务
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-16
    相关资源
    最近更新 更多