C# 确保异步任务在重新启动该任务之前已完全取消
好的,这里是这样的:我有一个应用程序的一部分,我在其中查询数据库中的行。当用户在搜索框中输入文本(或更改另一个过滤器设置)时,我执行查询 从数据库返回的数据将进入绑定到DataGrid的ObservableCollection。因为我意识到要保持UI的响应性,所以我使用Async Wait(尝试)在后台填充这个ObservableCollection 因此,在我看来,每当用户键入某些内容(或更改过滤器设置)时,我都想取消正在进行的任务,等待它确认已取消,然后使用新设置“重新启动”(或者创建新任务) 但是我得到了各种奇怪的结果(特别是当我减慢任务以模拟缓慢的数据库访问时),例如集合没有被清除并被填充两次,以及在处理CancellationTokenSource时(我读到这是一个好主意),有时当我要调用C# 确保异步任务在重新启动该任务之前已完全取消,c#,wpf,async-await,task,C#,Wpf,Async Await,Task,好的,这里是这样的:我有一个应用程序的一部分,我在其中查询数据库中的行。当用户在搜索框中输入文本(或更改另一个过滤器设置)时,我执行查询 从数据库返回的数据将进入绑定到DataGrid的ObservableCollection。因为我意识到要保持UI的响应性,所以我使用Async Wait(尝试)在后台填充这个ObservableCollection 因此,在我看来,每当用户键入某些内容(或更改过滤器设置)时,我都想取消正在进行的任务,等待它确认已取消,然后使用新设置“重新启动”(或者创建新任务
Cancel()时
在此期间它已被处理,我得到一个例外
我怀疑这个问题源于我对这里要使用的模式的理解有一个根本性的差距,因此任何样式/模式指针都像实际的技术解决方案一样受欢迎
代码基本上是这样的:
ObservableCollection<Thing> _thingCollection;
Task _thingUpdaterTask;
CancellationTokenSource _thingUpdaterCancellationSource;
// initialisation etc. here
async void PopulateThings(ThingFilterSettings settings)
{
// try to cancel any ongoing task
if(_thingUpdaterTask?.IsCompleted ?? false){
_thingUpdaterCancellationSource.Cancel();
await _thingUpdaterTask;
}
// I'm hoping that any ongoing task is now done with,
// but in reality that isn't happening. I'm guessing
// that's because Tasks are getting dereferenced and
// orphaned in concurrent calls to this method?
_thingCollection.Clear();
_thingUpdaterCancellationSource = new CancellationTokenSource();
var cancellationToken = _thingUpdaterCancellationSource.Token;
var progressHandler = new Progress<Thing>(x => _thingCollection.add(x));
var progress = (IProgress<Thing>)progressHandler;
try{
_thingUpdaterTask = Task.Factory.StartNew(
() => GetThings(settings, progress, cancellationToken));
await _thingUpdaterTask;
}catch(AggregateException e){
//handle stuff etc.
}finally{
// should I be disposing the Token Source here?
}
}
void GetThings(ThingFilterSettings settings,
IProgress<Thing> progress,
CancellationToken ctok){
foreach(var thingy in SomeGetThingsMethod(settings)){
if(ctok.IsCancellationRequested){
break;
}
progress.Report(thingy);
}
}
observetecollection\u thingCollection;
任务_thingUpdateTask;
CancellationTokenSource\u ThingUpdateCancellationSource;
//这里的初始化等
异步无效填充(ThingFilterSettings设置)
{
//尝试取消任何正在进行的任务
如果(_thingUpdateTask?.IsCompleted?false){
_thingUpdateCancellationSource.Cancel();
等待更新任务;
}
//我希望任何正在进行的任务现在都能完成,
//但事实上,我猜这并没有发生
//这是因为任务被取消引用,并且
//在对该方法的并发调用中孤立?
_thingCollection.Clear();
_ThingUpdateCancellationSource=新的CancellationTokenSource();
var cancellationToken=\u thingUpdateCancellationSource.Token;
var progressHandler=newprogress(x=>_thingCollection.add(x));
var progress=(IProgress)progressHandler;
试一试{
_ThingUpdateTask=Task.Factory.StartNew(
()=>GetThings(设置、进度、取消令牌));
等待更新任务;
}捕获(聚合异常e){
//处理东西等。
}最后{
//我应该在这里处理令牌源吗?
}
}
无效获取内容(内容过滤器设置,
我在进步,
取消令牌(ctok){
foreach(SomeGetThingsMethod(设置)中的var thingy){
如果(ctok.IsCancellationRequested){
打破
}
进度报告(thingy);
}
}
您可以添加一个包装类,它将在开始新任务之前等待上一个任务执行停止(通过完成或取消)
public class ChainableTask
{
private readonly Task _task;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public ChainableTask(Func<CancellationToken, Task> asyncAction,
ChainableTask previous = null)
{
_task = Execute(asyncAction, previous);
}
private async Task CancelAsync()
{
try
{
_cts.Cancel();
await _task;
}
catch (OperationCanceledException)
{ }
}
private async Task Execute(Func<CancellationToken, Task> asyncAction, ChainableTask previous)
{
if (previous != null)
await previous.CancelAsync();
if (_cts.IsCancellationRequested)
return;
await asyncAction(_cts.Token);
}
}
在本例中,创建的任务不支持取消,因此对
ChainableTask
的第二次调用将等待第一次任务。延迟(1000)
完成后,再调用第二次。等待取消异常,无需捕捉AggregateException
@dymanoid-我在尝试为示例概括代码时输入了一个错误。修正了,希望现在有意义@LexyStardust不使用async void
,事件处理程序除外。您应该更新该方法以返回Task
您的“void GetThings(…)”至少应该是“Task GetThings(…)”,否则它是一个“fire and forget”方法,if中的等待将始终立即返回。对于其余的部分,我觉得还可以。好的,我已经将“GetThings”更新为异步任务
(尽管Visual Studio现在警告我,那里没有等待的内容,可以吗?)。但不幸的是,这一更改实际上并没有对问题产生任何影响(在这两个方面-集合被多次填充而未被清除,如果我将令牌源的dispose放回,则会获得ObjectDisposedException)。。。
var firstAction = new ChainableTask(async tcs => await Task.Delay(1000));
var secondAction = new ChainableTask(async tcs => await Task.Delay(1000), firstAction ); // pass the previous action