取消异步操作

Cancel Async operation

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 中,当我连续点击按钮禁食时,我获得了数周的数据。

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

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

在您的示例代码中,您 很可能 想将 token 传递给 LoadDataFromDatabaseApplyDataToBindings,以及那些任务。

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

要记住的一件重要事情是 ThrowIfCancellationRequested 标记了 Task 中可以阻止 Task 的安全位置。没有保证运行时自动检测安全位置的安全方法。如果 Task 在请求取消后立即自动取消自身,它可能会处于未知状态,因此由开发人员标记这些安全位置。在您的 Task.

中多次调用检查取消的情况并不少见

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