Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To Cancel Any Current Parallel.ForEach And Start Fresh

Functionally have a long list of words bound to a ListView. Use a TextBox for chars to filter the list of words.

With any new char need to cancel any processing background filter. Then wait 1 second (DispatcherTimer) to start a fresh background parallel filter.

Have this working using BackGroundWorker but cannot translate the cancel-any-processing part to Parallel. Basically need "if (backgroundWorkerFTSfilter.IsBusy) backgroundWorkerFTSfilter.CancelAsync();" in Parallel.
If I am going about this wrong please let me know.

private List<FTSword> fTSwordsFiltered = new List<FTSword>();
CancellationTokenSource ftsCts = new CancellationTokenSource();
ParallelOptions ftspo = new ParallelOptions();
// in ctor ftspo.CancellationToken = ftsCts.Token;

public List<FTSword> FTSwordsFiltered  // ListView bound to
{
    get { return fTSwordsFiltered; }
    set
    {
        if (fTSwordsFiltered == value) return;
        fTSwordsFiltered = value;
        NotifyPropertyChanged("FTSwordsFiltered");
    }
}
public string FTSwordFilter            // TextBox bound to
{
    get { return fTSwordFilter; }
    set
    {
        if (value == fTSwordFilter) return;

        fTSwordFilter = value;
        NotifyPropertyChanged("FTSwordFilter");

        // cancel any filter currently processing
        ftsCts.Cancel();   // fts filter              
        // with BackgroundWorker this was able to cancel               
        // if (backgroundWorkerFTSfilter.IsBusy) backgroundWorkerFTSfilter.CancelAsync();

        dispatcherTimerFTSfilter.Stop();
        // wait 1 second and apply filter in background
        dispatcherTimerFTSfilter.Start();
    }
}
private void dispatcherTimerFTSfilter_Tick(object sender, EventArgs e)
{
    dispatcherTimerFTSfilter.Stop();
    List<FTSword> ftsWords = new List<FTSword>();
    //ftsCts = new CancellationTokenSource();  with these two it never cancels
    //ftspo.CancellationToken = ftsCts.Token;
    if (!(string.IsNullOrEmpty(FTSwordFilter)))
    {
        Task.Factory.StartNew(() =>
        {

            try
            {
                Parallel.ForEach(FTSwords, ftspo, ftsw =>
                {
                    if (ftsw.WordStem.StartsWith(FTSwordFilter))
                    {
                        ftsWords.Add(ftsw);
                    }
                    ftspo.CancellationToken.ThrowIfCancellationRequested();
                });
                Thread.Sleep(1000);   // so the next key stoke has time
                FTSwordsFiltered = (List<FTSword>)ftsWords;
            }
            catch (OperationCanceledException ei)
            {
                // problem is that it is always cancelled from the cancel request before DispatchTimer
                Debug.WriteLine(ei.Message);
            }
            Debug.WriteLine(ftsWords.Count.ToString() + "parallel ");
        });           
    }
}

The answer from Irman led me to this

    if (!(string.IsNullOrEmpty(FTSwordFilter)))
    {
        string startWorkFilter = FTSwordFilter;
        Task.Factory.StartNew(() =>
        {
            try
            {
                fTSwordsFilteredCancel = false;
                Parallel.ForEach(FTSwords, ftspo, (ftsw, loopstate) =>
                {
                    if (ftsw.WordStem.StartsWith(startWorkFilter))
                    {
                        ftsWords.Add(ftsw);
                    }
                    // Thread.Sleep(1);
                    if (fTSwordsFilteredCancel)
                    {
                        loopstate.Break();
                    }
                });
                Debug.WriteLine("fTSwordsFilteredCancel " + fTSwordsFilteredCancel.ToString());
                FTSwordsFiltered = (List<FTSword>)ftsWords;
                Debug.WriteLine(ftsWords.Count.ToString() + " parallel " + startWorkFilter);                       
            }
            catch (OperationCanceledException ei)
            {
                Debug.WriteLine(ei.Message);
            }
        });
    }

Very grateful for the answer I and will use this for some longer running tasks or longer list but got such great performance I moved this to the get (still with a 1 second delay). Results in a smaller memory footprint. Against 800,000 it runs in less than 1/10 second.

public IEnumerable<FTSword> FTSwordsFiltered
{
    get 
    { 
        if(string.IsNullOrEmpty(FTSwordFilter) || FTSwordFilter == "*") return FTSwords; 
        return FTSwords.AsParallel().Where(ftsWrd => ftsWrd.WordStem.StartsWith(FTSwordFilter));
    }
like image 217
paparazzo Avatar asked Jul 02 '12 19:07

paparazzo


People also ask

How do I cancel parallel ForEach?

Cancel a Parallel. For or Parallel. ForEach loop in . NET by supplying a cancellation token object to the method in the ParallelOptions parameter.

How do you break a parallel ForEachAsync?

The asynchronous API Parallel. ForEachAsync does not offer the Stop / Break functionality of its synchronous counterpart. One way to replicate this functionality is to use a bool flag in combination with the TakeWhile LINQ operator: bool breakFlag = false; await Parallel.

Does parallel ForEach wait for completion?

You don't have to do anything special, Parallel. Foreach() will wait until all its branched tasks are complete. From the calling thread you can treat it as a single synchronous statement and for instance wrap it inside a try/catch.

Is parallel ForEach blocking?

No, it doesn't block and returns control immediately. The items to run in parallel are done on background threads.


1 Answers

Parallel.ForEach comes with ParallelLoopState object. you can use this object to break the loop.

you can either use loopState.Break() or loopState.Stop() based on your requirements.

check this.

http://msdn.microsoft.com/en-us/library/dd991486.aspx

like image 140
Imran Amjad Avatar answered Oct 29 '22 16:10

Imran Amjad