简体   繁体   中英

How to cancel this Queued BackGroundworker

I have this bit of legacy code that's been sitting in the code-base and being used for the last couple of years, recently we found that its method StopImmediately() doesn't stop it at all. I cant make head nor tail of how it works as I've done very little with threads or background workers.

I was wondering if any of the experienced threading fellas could tell me how to stop this confusing little beast from completing its task. Below is the complete class (sorry for the amount of code)

I cannot figure out how to cancel it .. thanks in advance for any help ..

using System;
using System.Collections.Generic;

using System.ComponentModel;
using System.Threading;
namespace GPS2.BackgroundWorkerEx
{
public class QedBackgroundWorker
{
    public QedBackgroundWorker() {}

    Queue<object> Queue = new Queue<object>();          
    object lockingObject1 = new object();
    private Thread currentThread;
    public delegate void WorkerCompletedDelegate<K>(K result, Exception error);
    public object Arguments { get; set; }


    /// <summary>    
    /// doWork is a method with one argument    
    /// </summary>    
    /// <typeparam name="T">is the type of the input parameter</typeparam>    
    /// <typeparam name="K">is the type of the output result</typeparam>    
    /// <param name="inputArgument"></param>    
    /// <param name="doWork"></param>    
    /// <param name="workerCompleted"></param>    
     public void RunAsync<T,K>(Func<T, K> doWork, T inputArgument, WorkerCompletedDelegate<K> workerCompleted)    
     {        
         BackgroundWorker bw = GetBackgroundWorker<T,K>(doWork, workerCompleted);

         Queue.Enqueue(new QueueItem(bw, inputArgument));        
         lock (lockingObject1)        
         {            
             if (Queue.Count == 1)            
             {                
                 ((QueueItem)this.Queue.Peek()).RunWorkerAsync();
                 //currentThread = System.Threading.Thread.CurrentThread; 
             }        
         }    
     }    
    /// <summary>    
    /// Use this method if you don't need to handle when the worker is completed    
    /// </summary>    
    /// <param name="doWork"></param>    
    /// <param name="inputArgument"></param>    
     public void RunAsync<T,K>(Func<T, K> doWork, T inputArgument)    
     {        
         RunAsync(doWork, inputArgument, null);    
     }

    private BackgroundWorker GetBackgroundWorker<T, K>(Func<T, K> doWork, WorkerCompletedDelegate<K> workerCompleted)    
    {
        BackgroundWorker bw = new BackgroundWorker();

        bw.WorkerReportsProgress = false;        
        bw.WorkerSupportsCancellation = true;        
        bw.DoWork += (sender, args) =>{            
            if (doWork != null)  
            {
                args.Result = (K)doWork((T)args.Argument);
                currentThread = System.Threading.Thread.CurrentThread; 
            }        
        };        

        bw.RunWorkerCompleted += (sender, args) =>{            
            if (workerCompleted != null)            
            {                
                workerCompleted((K)args.Result, args.Error);            
            }            

            Queue.Dequeue();            

            lock (lockingObject1)            
            {                
                if (Queue.Count > 0)                
                {                    
                    ((QueueItem)this.Queue.Peek()).RunWorkerAsync();                                  
                }            
            }        
        };        
        return bw;    
    }
    public void StopImmediately()
    {
        if (currentThread != null)
            currentThread.Abort();
    }


    public bool IsBusy()
    {
        ThreadState state = currentThread.ThreadState;
        bool res = true;
        switch (state)
        {
            case ThreadState.Running:
                res =  true;
                break;
            default:
                res =  false;
                break;
        }
        return res;
    }

}
public class QueueItem{    


    public QueueItem(BackgroundWorker backgroundWorker, object argument)    
    {        

        this.BackgroundWorker = backgroundWorker;        
        this.Argument = argument;    
    }    

    public object Argument { get; private set; }    
    public BackgroundWorker BackgroundWorker { get; private set; }    

    public void RunWorkerAsync()    
    {        
        this.BackgroundWorker.RunWorkerAsync(this.Argument);    
    }    

}

}

The typical way for a BackgroundWorker to support cancellation is for the DoWork event handler to periodically check for the BackgroundWorker 's CancellationPending property:

var worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;  
worker.DoWork += (sender, args) =>
{            
    foreach (var thing in listOfThingsToDo)
    {
       if (worker.CancellationPending)
       {
            // Someone has asked us to stop doing our thing
            break;
       }
       else
       {
            DoTheThing(thing);
       }
    }
};   

Someone who wanted to cancel this BackgroundWorker , if it were running, would call

worker.CancelAsync();

which would cause it to set its CancellationPending property to true and cause the DoWork event handler that's currently running to break out of its loop after it finished processing the current thing . It wouldn't terminate immediately; the thing doing the work has to periodically ask "should I quit now or keep going?" Either way, the worker thread terminates gracefully.

The code written above tries to terminate the background worker immediately by aborting the thread. This is usually not the way that you want to do it. Ideally, this code should be changed so that the functions passed in as the doWork parameter regularly check for a cancellation request.

But the main reason that it doesn't work as written is that the currentThread member is not set until after the doWork function is invoked:

bw.DoWork += (sender, args) =>
{
    if (doWork != null)
    {
        args.Result = (K)doWork((T)args.Argument);
        currentThread = System.Threading.Thread.CurrentThread;
    }
};

A call to StopImmediately() would see a null thread, because the DoWork callback is waiting for the function passed in as doWork to finish before it assigns the currentThread member.

They should be flipped as follows

bw.DoWork += (sender, args) =>
{
    if (doWork != null)
    {
        currentThread = System.Threading.Thread.CurrentThread;
        args.Result = (K)doWork((T)args.Argument);
    }
};

and then it will indeed terminate uncleanly and (more or less) immediately. I recommend reading this MSDN article for an example of how to better approach supporting cancellation in a BackgroundWorker .

Hope this helps!

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