отменить асинхронную задачу, если она запущена

У меня есть следующий метод, вызываемый несколько раз (например, onkeyup текстового поля), который асинхронно фильтрует элементы в списке.

private async void filterCats(string category,bool deselect)
{

    List<Category> tempList = new List<Category>();
    //Wait for categories
    var tokenSource = new CancellationTokenSource();
    var token = tokenSource.Token;

    //HERE,CANCEL TASK IF ALREADY RUNNING


    tempList= await _filterCats(category,token);
    //Show results
    CAT_lb_Cats.DataSource = tempList;
    CAT_lb_Cats.DisplayMember = "strCategory";
    CAT_lb_Cats.ValueMember = "idCategory";

}

и следующее задание

private async Task<List<Category>> _filterCats(string category,CancellationToken token)
{
    List<Category> result = await Task.Run(() =>
    {
        return getCatsByStr(category);
    },token);

    return result;
}

и я хотел бы проверить, выполняется ли уже задача, и если да, отмените ее и запустите с новым значением. Я знаю, как отменить задачу, но как проверить, запущена ли она уже?


person m3div0    schedule 23.05.2015    source источник
comment
Вы можете добавить lock.   -  person Sybren    schedule 23.05.2015
comment
Вы хотите отменить текущую задачу, если для вашего фильтра поступает новое значение?   -  person eran otzap    schedule 23.05.2015
comment
@eran да, именно это я и собирался сделать   -  person m3div0    schedule 23.05.2015
comment
@Sybren, не могли бы вы объяснить это, пожалуйста?   -  person m3div0    schedule 23.05.2015
comment
вы должны изучить ReactiveExtentions, у которых есть оператор TakeLast.   -  person eran otzap    schedule 23.05.2015
comment
еще лучше stackoverflow.com/questions/18477018/   -  person eran otzap    schedule 23.05.2015
comment
Это вполне возможно сделать с помощью async и await.   -  person Philip Stuyck    schedule 23.05.2015
comment
@PhilipStuyck, это то, что я искал, как мне изменить свой код?   -  person m3div0    schedule 23.05.2015
comment
На второй взгляд @PhilipStuyck верно, было бы сложнее реализовать это с помощью rx . Я просто думаю, что вам не нужно идти по этому поводу, как это. Я хотел показать ему Throuttle и DistinctUntilCh\anged, которые обычно используются для фильтрации TextBox.   -  person eran otzap    schedule 23.05.2015
comment
@david Я отправил ответ, вам придется настроить его под свои нужды. Кто-то еще может захотеть опубликовать ответ, используя RX, это тоже сработает. Но я бы не стал использовать код, который я не совсем понимаю. Даже задержка, которую я делаю, вполне возможна и с использованием RX. RX стоит вашего времени на изучение.   -  person Philip Stuyck    schedule 23.05.2015
comment
@PhilipStuyck Спасибо, ваше решение очень хорошо подходит для моего кода.   -  person m3div0    schedule 24.05.2015
comment
Я отредактировал ваш заголовок. См. Должны ли вопросы включать «теги» в свои заголовки?, если нет единого мнения, не следует.   -  person John Saunders    schedule 24.05.2015


Ответы (2)


Это код, который я использую для этого:

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

_tokenSource = new CancellationTokenSource();

try
{
    await loadPrestatieAsync(_bedrijfid, _projectid, _medewerkerid, _prestatieid, _startDate, _endDate, _tokenSource.Token);
}
catch (OperationCanceledException ex)
{
}

а для вызова процедуры это так (конечно, упрощенно):

private async Task loadPrestatieAsync(int bedrijfId, int projectid, int medewerkerid, int prestatieid,
        DateTime? startDate, DateTime? endDate, CancellationToken token)
{
    await Task.Delay(100, token).ConfigureAwait(true);
    try{
        //do stuff

        token.ThrowIfCancellationRequested();

    }
    catch (OperationCanceledException ex)
    {
       throw;
    }
    catch (Exception Ex)
    {
        throw;
    }
}

Я делаю задержку в 100 мс, потому что одно и то же действие запускается довольно быстро и неоднократно, небольшая задержка в 100 мс делает вид, что на самом деле графический интерфейс более отзывчив.

person Philip Stuyck    schedule 23.05.2015
comment
предполагая, конечно, что filtercats() выполняется в том же потоке. наверное так и есть, он должен просто обратить внимание - person eran otzap; 23.05.2015

Похоже, вы ищете способ получить «список автозаполнения» из текста, введенного в текстовое поле, где текущий асинхронный поиск отменяется, когда текст изменился с момента начала поиска.

Как упоминалось в комментариях, Rx (Reactive Extensions) предоставляет для этого очень хорошие шаблоны, позволяющие легко подключать элементы пользовательского интерфейса к отменяемым асинхронным задачам, встраивая логику повторных попыток и т. д.

Нижеприведенная программа длиной менее 90 строк показывает пример «полного пользовательского интерфейса» (к сожалению, без котов ;-). Он включает в себя некоторые отчеты о статусе поиска.

простой пользовательский интерфейс

Я создал это, используя ряд статических методов в классе RxAutoComplete, чтобы показать, как это достигается с помощью небольших задокументированных шагов и как их можно комбинировать для достижения более сложной задачи.

namespace TryOuts
{
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using System.Reactive.Linq;
    using System.Threading;

    // Simulated async search service, that can fail.
    public class FakeWordSearchService
    {
        private static Random _rnd = new Random();
        private static string[] _allWords = new[] {
            "gideon", "gabby", "joan", "jessica", "bob", "bill", "sam", "johann"
        };

        public async Task<string[]> Search(string searchTerm, CancellationToken cancelToken)
        {
            await Task.Delay(_rnd.Next(600), cancelToken); // simulate async call.
            if ((_rnd.Next() % 5) == 0) // every 5 times, we will cause a search failure
                throw new Exception(string.Format("Search for '{0}' failed on purpose", searchTerm));
            return _allWords.Where(w => w.StartsWith(searchTerm)).ToArray();
        }
    }

    public static class RxAutoComplete
    {
        // Returns an observable that pushes the 'txt' TextBox text when it has changed.
        static IObservable<string> TextChanged(TextBox txt)
        {
            return from evt in Observable.FromEventPattern<EventHandler, EventArgs>(
                h => txt.TextChanged += h,
                h => txt.TextChanged -= h)
                select ((TextBox)evt.Sender).Text.Trim();
        }

        // Throttles the source.
        static IObservable<string> ThrottleInput(IObservable<string> source, int minTextLength, TimeSpan throttle)
        {
            return source
                .Where(t => t.Length >= minTextLength) // Wait until we have at least 'minTextLength' characters
                .Throttle(throttle)      // We don't start when the user is still typing
                .DistinctUntilChanged(); // We only fire, if after throttling the text is different from before.
        }

        // Provides search results and performs asynchronous, 
        // cancellable search with automatic retries on errors
        static IObservable<string[]> PerformSearch(IObservable<string> source, FakeWordSearchService searchService)
        {
            return from term in source // term from throttled input
                   from result in Observable.FromAsync(async token => await searchService.Search(term, token))
                        .Retry(3)          // Perform up to 3 tries on failure
                        .TakeUntil(source) // Cancel pending request if new search request was made.
                   select result;
        }

        // Putting it all together.
        public static void RunUI()
        {
            // Our simple search GUI.
            var inputTextBox = new TextBox() { Width = 300 };
            var searchResultLB = new ListBox { Top = inputTextBox.Height + 10, Width = inputTextBox.Width };
            var searchStatus = new Label { Top = searchResultLB.Height + 30, Width = inputTextBox.Width };
            var mainForm = new Form { Controls = { inputTextBox, searchResultLB, searchStatus }, Width = inputTextBox.Width + 20 }; 

            // Our UI update handlers.
            var syncContext = SynchronizationContext.Current;
            Action<Action> onUITread = (x) => syncContext.Post(_ => x(), null);
            Action<string> onSearchStarted = t => onUITread(() => searchStatus.Text = (string.Format("searching for '{0}'.", t)));
            Action<string[]> onSearchResult = w => { 
                searchResultLB.Items.Clear(); 
                searchResultLB.Items.AddRange(w);
                searchStatus.Text += string.Format(" {0} maches found.", w.Length > 0 ? w.Length.ToString() : "No");
            };

            // Connecting input to search
            var input = ThrottleInput(TextChanged(inputTextBox), 1, TimeSpan.FromSeconds(0.5)).Do(onSearchStarted);
            var result = PerformSearch(input, new FakeWordSearchService());

            // Running it
            using (result.ObserveOn(syncContext).Subscribe(onSearchResult, ex => Console.WriteLine(ex)))
                Application.Run(mainForm);
        }
    }
}
person Alex    schedule 24.05.2015