在重新启动该任务之前确保异步任务已完全取消

时间:2017-09-13 15:18:56

标签: c# wpf async-await task

好的,这里是:我有一个应用程序的一部分,我从数据库查询行。当用户在搜索框中输入文本(或更改其他过滤器设置)时,我会执行查询。

从数据库返回的数据将进入一个绑定到DataGrid的ObservableCollection。因为我意识到保持UI响应,我正在使用Async-Await(尝试)在后台填充此ObservableCollection。

所以,在我看来,每当用户输入内容(或更改过滤器设置)时,我想取消正在进行的任务等待它确认已取消然后“重启”(或而是使用新设置创建一个新任务。

但是我得到各种奇怪的结果(特别是当我放慢任务以模拟慢速数据库访问时),例如集合没有被清除并且被填充两次并且在处理CancellationTokenSource时(我读到的是一个很好的)想法)有时当我到达调用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);
    }
}

1 个答案:

答案 0 :(得分:0)

您可以添加一个包装类,它将在开始新任务之前等待上一个任务执行停止(通过完成或取消)。

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);
    }
}

如果在以前的项目中使用上面的类。该类使用lambda asyncAction来创建下一个任务。该任务仅在上一个完成后创建。

它会将CancellationToken传递给每个任务,以便在完成任务之前停止任务。在开始下一个任务之前,前一个令牌被取消,等待上一个任务。这发生在CancelAsync中。

只有在等待上一个Cancel之后,我们才会调用lambda来创建下一个任务。

用法示例:

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

在此示例中,创建的任务不支持取消,因此第二次调用ChainableTask将等到第一个Task.Delay(1000)完成,然后再调用第二个。{/ p>