简体   繁体   中英

cancel async task if running

I have the following method called on several occasions (eg onkeyup of textbox) which asynchronously filters items in listbox.

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";

}

and the following task

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

    return result;
}

and I would like to test whether the task is already runing and if so cancel it and start it with the new value. I know how to cancel task, but how can I check whether it is already running?

This is the code that I use to do this :

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

_tokenSource = new CancellationTokenSource();

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

and for the procedure call it is like this (simplified of course) :

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

I am doing a delay of 100 ms because the same action is triggered rather quickly and repeatedly, a small postpone of 100 ms makes it look like the GUI is more responsive actually.

It appears you are looking for a way to get an "autocomplete list" from text entered in a text box, where an ongoing async search is canceled when the text has changed since the search was started.

As was mentioned in the comments, Rx (Reactive Extensions), provides very nice patterns for this, allowing you to easily connect your UI elements to cancellable asynchronous tasks, building in retry logic, etc.

The less than 90 line program below, shows a "full UI" sample (unfortunately excluding any cats ;-). It includes some reporting on the search status.

简单的UI

I have created this using a number of static methods in the RxAutoComplete class, to show how to this is achieved in small documented steps, and how they can be combined, to achieve a more complex task.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM