如何检查以前的任务是否仍在运行并停止/取消?

时间:2014-06-24 07:20:59

标签: c# taskfactory

我有一个后台任务,我按以下方式运行。

这是对文本框文本更改事件的附加行为。

我想要的是,如果文本被更改然后再次更改,则在第二次更改时检查上一个任务是否仍在运行,如果是,请将其停止并继续使用最新任务。

public class FindTextChangedBehavior : Behavior<TextBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.TextChanged += OnTextChanged;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.TextChanged -= OnTextChanged;
        base.OnDetaching();
    }

    private void OnTextChanged(object sender, TextChangedEventArgs args)
    {
        var textBox = (sender as TextBox);
        if (textBox != null)
        {
            Task.Factory.StartNew(() =>
            {
                //Do text search on object properties within a DataGrid
                //and populate temporary ObservableCollection with items.
                ClassPropTextSearch.init(itemType, columnBoundProperties);

                if (itemsSource != null)
                {
                    foreach (object o in itemsSource)
                    {
                        if (ClassPropTextSearch.Match(o, searchValue))
                        {
                            tempItems.Add(o);
                        }
                    }
                }

                //Copy temporary collection to UI bound ObservableCollection 
                //on UI thread
                Application.Current.Dispatcher.Invoke(new Action(() => MyClass.Instance.SearchMarkers = tempItems));
            });
        }
    }

[编辑]我还没有对此进行测试,只是一个可能的模型。

CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();

private void OnTextChanged(object sender, TextChangedEventArgs args)
{
    var newCts = new CancellationTokenSource();
    var oldCts = Interlocked.Exchange(ref this.CancellationTokenSource, newCts);

    if (oldCts != null)
    {
        oldCts.Cancel();
    }

    var cancellationToken = newCts.Token;

    var textBox = (sender as TextBox);
    if (textBox != null)
    {
        ObservableCollection<Object> tempItems = new ObservableCollection<Object>();
        var ui = TaskScheduler.FromCurrentSynchronizationContext();

        var search = Task.Factory.StartNew(() =>
        {
            ClassPropTextSearch.init(itemType, columnBoundProperties);

            if (itemsSource != null)
            {
                foreach (object o in itemsSource)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    if (ClassPropTextSearch.Match(o, searchValue))
                    {
                        tempItems.Add(o);
                    }
                }
            }
        }, cancellationToken);

        //Still to be considered.
        //If it gets to here and it is still updating the UI then 
        //what to do, upon SearchMarkers being set below do I cancel
        //or wait until it is done and continue to update again???
        var displaySearchResults = search.ContinueWith(resultTask =>
                     MyClass.Instance.SearchMarkers = tempItems,
                     CancellationToken.None,
                     TaskContinuationOptions.OnlyOnRanToCompletion,
                     ui);
    }
}

enter image description here

2 个答案:

答案 0 :(得分:4)

我有点担心你提议在非UI线程上搜索“DataGrid中的对象属性” - 这可能非常有用,因为你不是设置来自后台的任何值线程,但有一点气味。

暂时忽略这一点,让我提出以下解决方案:

private readonly SemaphoreSlim Mutex = new SemaphoreSlim(1, 1);
private CancellationTokenSource CancellationTokenSource;

private void OnTextChanged(object sender, TextChangedEventArgs args)
{
    var newCts = new CancellationTokenSource();
    var oldCts = Interlocked.Exchange(ref this.CancellationTokenSource, newCts);

    if (oldCts != null)
    {
        oldCts.Cancel();
    }

    var cancellationToken = newCts.Token;

    var textBox = (sender as TextBox);
    if (textBox != null)
    {
        // Personally I would be capturing
        // TaskScheduler.FromCurrentSynchronizationContext()
        // here and then scheduling a continuation using that (UI) scheduler.
        Task.Factory.StartNew(() =>
        {
            // Ensure that only one thread can execute
            // the try body at any given time.
            this.Mutex.Wait(cancellationToken);

            try
            {
                cancellationToken.ThrowIfCancellationRequested();

                RunSearch(cancellationToken);

                cancellationToken.ThrowIfCancellationRequested();

                //Copy temporary collection to UI bound ObservableCollection 
                //on UI thread
                Application.Current.Dispatcher.Invoke(new Action(() => MyClass.Instance.SearchMarkers = tempItems));
            }
            finally
            {
                this.Mutex.Release();
            }
        }, cancellationToken);
    }
}

修改

由于我现在知道您的目标是async - 感知框架,因此上述解决方案可以简化和增强。

我必须对如何收集“网格属性”进行大量假设,并尝试将该进程(我认为应该在调度程序线程上运行)与实际搜索(我正在安排的)进行解耦线程池)。

public class FindTextChangedBehavior : Behavior<TextBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.TextChanged += OnTextChanged;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.TextChanged -= OnTextChanged;
        base.OnDetaching();
    }

    private CancellationTokenSource CancellationTokenSource;

    // We're a UI handler, hence async void.
    private async void OnTextChanged(object sender, TextChangedEventArgs args)
    {
        // Assume that this always runs on the UI thread:
        // no thread safety when exchanging the CTS.
        if (this.CancellationTokenSource != null)
        {
            this.CancellationTokenSource.Cancel();
        }

        this.CancellationTokenSource = new CancellationTokenSource();

        var cancellationToken = this.CancellationTokenSource.Token;

        var textBox = (sender as TextBox);
        if (textBox != null)
        {
            try
            {
                // If your async work completes too quickly,
                // the dispatcher will be flooded with UI
                // update requests producing a laggy user
                // experience. We'll get around that by
                // introducing a slight delay (throttling)
                // before going ahead and performing any work.
                await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);

                // Reduce TaskCanceledExceptions.
                // This is async void, so we'll just
                // exit the method instead of throwing.

                // IMPORTANT: in order to guarantee that async
                // requests are executed in correct order
                // and respond to cancellation appropriately,
                // you need to perform this check after every await.
                // THIS is the reason we no longer need the Semaphore.
                if (cancellationToken.IsCancellationRequested) return;

                // Harvest the object properties within the DataGrid.
                // We're still on the UI thread, so this is the
                // right place to do so.
                IEnumerable<GridProperty> interestingProperties = this
                    .GetInterestingProperties()
                    .ToArray(); // Redundant if GetInterestingProperties returns a
                                // list, array or similar materialised IEnumerable.

                // This appears to be CPU-bound, so Task.Run is appropriate.
                ObservableCollection<object> tempItems = await Task.Run(
                    () => this.ResolveSearchMarkers(interestingProperties, cancellationToken)
                );

                // Do not forget this.
                if (cancellationToken.IsCancellationRequested) return;

                // We've run to completion meaning that
                // OnTextChanged has not been called again.
                // Time to update the UI.
                MyClass.Instance.SearchMarkers = tempItems;
            }
            catch (OperationCanceledException)
            {
                // Expected.
                // Can still be thrown by Task.Delay for example.
            }
            catch (Exception ex)
            {
                // This is a really, really unexpected exception.
                // Do what makes sense: log it, invalidate some
                // state, tear things down if necessary.
            }
        }
    }

    private IEnumerable<GridProperty> GetInterestingProperties()
    {
        // Be sure to return a materialised IEnumerable,
        // i.e. array, list, collection.
        throw new NotImplementedException();
    }

    private ObservableCollection<object> ResolveSearchMarkersAsync(
        IEnumerable<GridProperty> interestingProperties, CancellationToken cancellationToken)
    {
        var tempItems = new ObservableCollection<object>();

        //Do text search on object properties within a DataGrid
        //and populate temporary ObservableCollection with items.
        foreach (var o in interestingProperties)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (ClassPropTextSearch.Match(o, searchValue))
            {
                tempItems.Add(o);
            }
        }

        return tempItems;
    }
}

答案 1 :(得分:1)

如果您传入取消令牌,从CancellationTokenSource传递到任务并通过递增变量来保持您更改文本的次数,您将能够通过调用来检查此值并取消任务token.Cancel(),并调用ThrowIfCancellationRequested,它会引发OperationCancelled异常。

必须通过TaskContinuationOptions.OnlyOnCanceled传递延续,以便仅在引发取消时执行,否则在其他任务完成时将引发。 (代码示例)