取消在.Net

时间:2018-05-05 07:17:01

标签: .net vb.net task task-parallel-library cancellationtokensource

这似乎是一个常见问题,但我还无法找到解决方案。我在SO中检查过这个 Cancelling a Task is throwing an exception

我的来电者:

    Private Async Sub btnTestTimer_Click(sender As Object, e As EventArgs) Handles btnTest.Click
    _cts = New CancellationTokenSource()
    Try
        Await Task.Run(AddressOf TestCancellationAsync).ConfigureAwait(False)
    Catch cx As OperationCanceledException
        MsgBox(String.Format("The following error occurred: {0}", cx.Message), MsgBoxStyle.Critical)
    Catch ex As Exception
        MsgBox(String.Format("The following error occurred: {0}", ex.Message), MsgBoxStyle.Critical)
    End Try
End Sub

我的任务就在这里

    Private Async Function TestCancellationAsync() As Task
        'Launch a dummy timer which after some time will itself cancel a token and throw
        Dim tmr As New System.Timers.Timer(1000)
        AddHandler tmr.Elapsed, AddressOf OnTimerElapsed
        tmr.Enabled = True
    End Function

取消和退出的计时器功能是

    Private Sub OnTimerElapsed(sender As Object, e As ElapsedEventArgs)
        Dim tmr As System.Timers.Timer = CType(sender, System.Timers.Timer)
        tmr.Enabled = False
        Task.Delay(5000) 'After 5 seconds simulate a cancellation
        _cts.Cancel() //This is just to cancel from within the timer, actually the cancellation to _cts will happen from another caller which is not shown here
        _cts.Token.ThrowIfCancellationRequested()
    End Sub

此处未显示具有异步任务和取消的实际程序,以使示例简洁,同时仍能够复制问题。

业务要求是,在单击按钮时,将启动异步任务,这将打开多个异步功能。其中一个将启动一个计时器,它将继续检查_cts令牌状态并在需要时取消。如果在_cts令牌上从外部发生这种取消,则计时器将抛出取消异常

我尝试过的事情:

  • 我已经处理了OperationCancelled异常,但仍然没有去那里。
  • 我取消选中了Tools-Options-Debug-General-只启用我的代码,看它是否只是Visual Studio。但它仍被报告为PDB未处理的例外
  • 我从外部运行了exe,正如预期的那样,由于未处理的异常而崩溃了

请让我知道我在这里做错了什么。我的调用者等待任务完成 - 由于计时器从任务内部运行,我将期望任务未完成,并且将捕获任何引发的异常。

1 个答案:

答案 0 :(得分:0)

我认为在这种情况下,计时器就是问题所在。创建并具有从任务内部触发然后从计时器处理方法中抛出取消异常的计时器不起作用,因为只要创建并启用计时器,任务就会返回而不等待计时器完成。这意味着int getIndex(size_t index){ return index; } template<typename... Args> int getIndex(size_t index, Args... args){ return access_multiplier[DIM-sizeof...(Args)-1]*index + getIndex(args...); } template <typename... Args, typename std::enable_if_t<sizeof...(Args) == DIM, int> = 0> T& get(Args&&... args){ return data[getIndex(args...)]; /*std::array<size_t, DIM> idx_copy{args...}; size_t index = idx_copy[DIM-1]; for(int i = DIM-2; i >= 0; --i){ index += idx_copy[i]*access_multiplier[i]; } return data[index];*/ } 方法不会等待计时器代码触发并保持任务。它立即返回给调用者。 TestCancellationAsync内的调用者认为该任务已经返回并且从try中退出并结束该方法。这意味着没有有效的事件处理程序来捕获计时器抛出的异常。这会导致未处理的异常。

解决方案是通过使用具有相关延迟的无限循环来模拟计时器,并从中调用计时器代码而不创建计时器对象。

所以btnTestTimer_Click应该改为看起来像这样

TestCancellationAsync

然后实际上是worker函数的 Private Async Function TestCancellationAsync() As Task 'Simulate a timer behaviour While True Await DoWorkAsync().ConfigureAwait(False) Await Task.Delay(1000).ConfigureAwait(False) End While End Function 可以改为

OnTimerElapsed

现在if _cts从外面被取消,它被抓住了。

这解决了当前的问题。