1

次の方法で実行するバックグラウンド タスクがあります。

これは、テキスト ボックスのテキスト変更イベントに付随する動作です。

私が望むのは、テキストが変更されてから再度変更された場合、2回目の変更で前のタスクがまだ実行されているかどうかを確認し、そうであれば停止して最新のタスクを続行することです。

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

ここに画像の説明を入力

4

2 に答える 2

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;
    }
}
于 2014-06-24T23:50:45.217 に答える
1

CancellationTokenSource からタスクにキャンセル トークンを渡し、変数をインクリメントしてテキストを変更した回数をカウントすると、token.Cancel を呼び出してこの値を確認し、タスクをキャンセルできます。 () を呼び出し、OperationCancelled 例外を発生させる ThrowIfCancellationRequested を呼び出します。

継続は、キャンセルが発生したときにのみ実行されるように TaskContinuationOptions.OnlyOnCanceled を渡す必要があります。そうしないと、他のタスクが終了したときに発生します。(以下のコード例)

于 2014-06-24T08:15:21.560 に答える