正确使用TPL启动可取消的ASync操作

时间:2015-07-28 14:47:42

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

我一直在使用TPL在非UI线程中运行数据库提取,允许UI在它们发生时继续运行。调用以下示例中的代码以在主详细信息视图中填充详细信息窗格。主窗格中有一个树视图,根据单击的节点提取不同的数据。 UI允许用户取消提取,并在提取处于活动状态时选择其他节点时自动取消提取。这是我用来执行此操作的代码:

 Protected Overrides Sub FetchSummary()
  If DBKey.PresentAndSet(DataKey) Then
    _view.BeginDataFetch()
    ' Cancel any active refresh
    If TaskCancelTokenSource IsNot Nothing Then TaskCancelTokenSource.Cancel()
    TaskCancelTokenSource = New CancellationTokenSource
    Dim ctok = TaskCancelTokenSource.Token
    Dim dataTask = New Task(Of IEnumerable(Of IAssignSailingPart.ISummary))(Function() FetchsummaryData(Context, DataKey), ctok)
    Dim uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext
    dataTask.ContinueWith(Sub(dt) _view.Data = dt.Result, ctok, TaskContinuationOptions.OnlyOnRanToCompletion, uiSyncContext)
    dataTask.ContinueWith(Sub(dt) _view.FailDataFetch("There was an error fetching the data, try refreshing"), Nothing, TaskContinuationOptions.OnlyOnFaulted, uiSyncContext)
    dataTask.ContinueWith(Sub(dt) _view.Data = New List(Of IAssignSailingPart.ISummary), Nothing, TaskContinuationOptions.OnlyOnCanceled, uiSyncContext)
    dataTask.Start()
  End If
End Sub

因此,为了启动任务,我们调用一个函数来查询数据库以获得结果。成功之后我们将其发送到视图,在取消时我们将一个空数据集发送到视图,并且在失败时我们告诉用户尝试刷新。

这个似乎可以正常工作。用户对响应性等感到满意。我们最近遇到了一些问题,尽管数据库服务器遇到了一些无关的问题。如果在应用程序的编译版本上获取失败(而不是在IDE中),则在实际失败发生后不久,它将使用未捕获的聚合异常终止应用程序。我已经对此进行了一些研究并理解(或者认为我这样做)异常在任务被垃圾收集时抛出在不同的线程上。

我的问题是我应该如何调整代码来正确处理这个问题?这适用于使用.Net 4.0的Windows窗体应用程序。

1 个答案:

答案 0 :(得分:1)

您遇到的问题与未在故障任务中观察异常有关(在本例中为dt)。每个Task对象都带有一个标志,指示是否观察/访问了它的异常 - 如果有的话。当Task对象最终完成并且该标志指示未处理异常时,应用程序将被.NET 4.0关闭。顺便说一句,这是在.NET 4.5中已经改变的行为。 Stephen Toub详细解释了here

正确处理此问题的方法是在访问dt.Exception之前查看dt.Result。当您访问dt.Exception属性时(为了决定下一步做什么,或者只是为了记录异常),这将导致Task异常被标记为已观察,并且应用程序将不再在Task实例的最终确定时崩溃。另一方面,如果dt.Result出现故障,直接访问Task只会传播(重新抛出)异常。

我也会进行一次ContinueWith()调用,并在那里检查Task状态(原谅我的VB,我是C#dev):

 Protected Overrides Sub FetchSummary()
  If DBKey.PresentAndSet(DataKey) Then
    _view.BeginDataFetch()
    ' Cancel any active refresh
    If TaskCancelTokenSource IsNot Nothing Then TaskCancelTokenSource.Cancel()
    TaskCancelTokenSource = New CancellationTokenSource
    Dim ctok = TaskCancelTokenSource.Token
    Dim dataTask = New Task(Of IEnumerable(Of IAssignSailingPart.ISummary))(Function() FetchsummaryData(Context, DataKey), ctok)
    Dim uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext
    dataTask.ContinueWith(Sub(dt) 
       If dt.IsFaulted Then
           _view.FailDataFetch("There was an error fetching the data, try refreshing")
           Exit Sub
       End If
       If dt.IsCancelled Then
           _view.Data = New List(Of IAssignSailingPart.ISummary)
           Exit Sub
       End If

       _view.Data = dt.Result
    End Sub, ctok, uiSyncContext)
    dataTask.Start()
  End If
End Sub

原因是,当您使用Task标记TaskContinuationOptions.OnlyOnRanToCompletion时,如果Task未运行到Cancelled,那么该延续dataTask将被标记为function verifyWorkout(workout, wtype){ var errorlist = ""; try{ for(i=1;i<workout.length;i++){ var cardiocount = 0; var strengthcount = 0; var corecount = 0; var hiitcount = 0; for(j=1;j<workout[i].length;j++){ for(k=0;k<workout[i][j].length;k++){ if (workout[i][j][k].extype == "Cardio"){ cardiocount += cardiocount + 1; }else if (workout[i][j][k].extype == "Strength"){ strengthcount += strengthcount + 1; }else if (workout[i][j][k].extype == "Core"){ corecount += corecount + 1; }else if (workout[i][j][k].extype == "Hiit"){ hiitcount += hiitcount + 1; } } } if (wtype === "Beginner"){ if (i = 1){ if (cardiocount < 1){ errorlist = errorlist + "Week 1 requires at least 1 cardio exercise.</br>"; } if (strengthcount < 1){ errorlist = errorlist + "Week 1 requires at least 1 strength exercise.</br>"; } if (corecount < 1){ errorlist = errorlist + "Week " + i + " requires at least 1 core exercise.</br>"; } } else if (i = 2){ if (cardiocount < 2){ errorlist = errorlist + "Week 2 requires at least 2 cardio exercises.</br>"; } if (strengthcount < 1){ errorlist = errorlist + "Week 2 requires at least 1 strength exercise.</br>"; } if (corecount < 1){ errorlist = errorlist + "Week " + i + " requires at least 1 core exercise.</br>"; } } else { if (cardiocount < 3){ errorlist = errorlist + "Week " + i + " requires at least 3 cardio exercises.</br>"; } if (strengthcount < 2){ errorlist = errorlist + "Week " + i + " requires at least 2 strength exercises.</br>"; } if (corecount < 1){ errorlist = errorlist + "Week " + i + " requires at least 1 core exercise.</br>"; } } }else if (wtype === "Intermediate"){ if (cardiocount < 3){ errorlist = errorlist + "Week " + i + " requires at least 3 cardio exercises.</br>"; } if (strengthcount < 2){ errorlist = errorlist + "Week " + i + " requires at least 2 strength exercises.</br>"; } if (corecount < 1){ errorlist = errorlist + "Week " + i + " requires at least 1 core exercise.</br>"; } }else if (wtype === "Advanced"){ if (cardiocount < 2){ errorlist = errorlist + "Week " + i + " requires at least 2 cardio exercises.</br>"; } if (strengthcount < 2){ errorlist = errorlist + "Week " + i + " requires at least 2 strength exercises.</br>"; } if (hiitcount < 2){ errorlist = errorlist + "Week " + i + " requires at least 2 hiit exercises.</br>"; } if (corecount < 1){ errorlist = errorlist + "Week " + i + " requires at least 1 core exercise.</br>"; } } } return errorlist; } catch(err) { console.log("error retrieving validation...."); console.log(errorlist); console.log(err); } } 完成,只会使问题复杂化。