取消异步操作

3
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和cancellation code整个方法。 - Orel Eraki
我刚刚注意到你的TriggerWeekChanged方法是async void。请看一下我在答案末尾添加的注释。 - Bradley Uffner
@BradleyUffner,那是我在创建问题时犯的一个错误。在我的代码中它是Task类型。感谢您的提醒! - Felix D.
2个回答

3

您没有提及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
}

2
Task.WaitAll isn't an option since it totally blocks my UI-Thread... That's why I was using async/await - Felix D.
那么我会针对全局的 Thread 并将所有在 TriggerWeekChanged 中的东西发送到那里。或者,如果您不需要在 //Refresh Passed 中的代码,并且您完全意识到将这些方法发送到其他未等待任务中的后果,则可以简单地删除 Task.WaitAll - erexo
2
@Erexo 没有必要使用这样的回归。你可以使用await Task.WhenAll()。此外,异步并不意味着后台线程参与其中。真正的异步IO操作不使用线程。 - Panagiotis Kanavos
事实上,“Task.WaitAll”只会破坏问题的主要目的。当执行时,任务无法被终止,因为由于“Task.WaitAll”的UI线程阻塞,玩家将无法再次点击按钮。 - erexo

3
所有等待的内容都需要在该CancellationToken上有可见性。如果它是你编写的自定义Task,它应该将其作为参数接受,并定期在函数内部检查是否已被取消。如果是这样,它应该采取任何必要的行动(如果有的话)来停止或回滚正在进行的操作,然后调用ThrowIfCancellationRequested()。
在你的示例代码中,你很可能想把token传递给LoadDataFromDatabase和ApplyDataToBindings,以及这些任务的任何子任务。
可能存在一些更高级的情况,在这些情况下你不想将相同的CancellationToken传递给子任务,但你仍然希望它们能够被取消。在这些情况下,你应该在Task内部创建一个新的CancellationToken,只用于子任务。
需要记住的重要事情是,ThrowIfCancellationRequested标识了Task中可以安全停止的位置。运行时并没有保证自动检测安全位置的安全方式。如果Task在取消请求后自动取消自己,那么它可能会处于未知状态,因此开发人员需要标记这些安全位置。在Task中遍布多个检查取消的调用并不罕见。
我刚刚注意到你的TriggerWeekChanged函数是async void。通常情况下,除了事件处理程序之外,将async void用于其他东西通常被认为是一种反模式。它可能会在方法内跟踪异步操作的完成状态和处理任何可能从其中抛出的异常方面产生很多问题。如果不是事件处理程序,被标记为async void的任何东西都应该非常小心,因为99%或更多的时间都是错误的做法。我强烈建议将其改为async Task,并考虑从你的其他代码中传递CancellationToken。

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