user7194131
user7194131

Reputation: 45

How can i kill or stop backgroundworker?

To load several large files quickly, I launched backgroundworkers as the number of files.

Each backgroundworker needs long time to load its file respectively. While they load their files, I'd like to stop all loading. I understand backgroundworker.CancelAsync() sends cancellation message to the thread, but the thread has no time to accept the message. Because each thread is loading just one file and there is no loop operation to check cancellation. In this case, how can I stop these backgroundworkers?

Let me show my codes here. //main thread calls 50 child threads.

private List<BackgroundWorker> bgws = new List<BackgroundWorker>();
private bool ChildThreadCompleted;
 private void MainThread_DoWork(object sender, DoWorkEventArgs e)
{
// 50 sub threads will be started here
    for (int i=1; i<=50; i++)
    {
        if (mainThread.CancellationPending) return;
        BackgroundWorker childThread = new BackgroundWorker();
        childThread.WorkerSupportsCancellation = true;
        childThread.DoWork += ChildThread_DoWork;
        childThread.RunWorkerCompleted += ChildThread_RunWorkerCompleted;
        bgws.Add(childThread);
        childThread.RunWorkerAsync(i);
    }
    while (!ChildThreadCompleted)
    {
        if (mainThread.CancellationPending)
        {
            foreach (BackgroundWorker thread in bgws)
                if (thread.IsBusy) thread.CancelAsync();
        }
        Application.DoEvents();
    }
}

 private void ChildThread_DoWork(object sender, DoWorkEventArgs e)
{
    int arg = Convert.ToInt32(e.Argument);
    System.Threading.Thread.Sleep(1000);
    BackgroundWorker thread = (BackgroundWorker)sender;
    if (thread.CancellationPending) return;
    // In case of loading the image no longer makes sense, I'd like to stop.
    // At this point, i can't stop this process.
    //loading large file here. Just one image file for example. <= this takes one or more seconds

    e.Result = arg;
}

private void ChildThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    BackgroundWorker thread = sender as BackgroundWorker;
    bgws.Remove(thread);
    if (bgws.Count == 0) ChildThreadCompleted = true;
}

Upvotes: 0

Views: 291

Answers (1)

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131237

Short version

BGW can't do that, not without complex coding. ActionBlock was purpose-built to handle a stream of inputs with cancellation support.

I use the dataflow classes to find, download and proccess thousands of air ticket records every 15 minutes.

Long Version

BackgroundWorker is obsolete, fully replaced by TPL classes like tasks, CancellationToken, IProgress etc.

This particular problem though is best addressed by a higher-level class, ActionBlock. ActionBlock and the other classes in the TPL Dataflow namespace allow you to create a pipeline of blocks similar to a Powershell pipeline.

Each block runs on its own task, receives an input and passes an output to the next block. You can even specify that one block can process multiple inputs by using multiple tasks.

ActionBlock doesn't produce any output, it only processes inputs. Like most blocks, it has an input buffer and supports cancellation.

A file processing block could look like this. :

var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file));

var files = Directory.EnumerateFiles(someFolder,somePattern);

//Post all files
foreach(file in files)
{
    //Post doesn't block
    block.Post(file);
}    

When we are done using the block, we should tell it we're finished :

block.Complete();

And await asynchronously until all leftover files are processed :

await block.Completion;

You can instruct the block to process multiple messages in parallel by specifying the maximum number of concurrent tasks :

var options = new ExecutionDataflowBlockOptions
     {
        MaxDegreeOfParallelism = 20
     };

var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file), options);

Starting 100 threads to process 100 files will probably result in the threads blocking each other. By limiting the number of concurrent tasks you ensure all the CPUs can perform useful work.

You can also stop the block. One way is to "nuke" it by calling the Fault() method :

block.Fault();
try 
{
    await block.Completion;
}
catch(Exception exc)
{
     ...
}

This will discard everything left into the input buffer and propagate an exception to the next block in the pipeline. It's as if the block's method threw an exception. In this case there's no other block and await block.Completion; will throw.

Another, more cooperative way is to use a CancellationTokenSource to cancel the block and signal the worker method that it should cancel.

A CancellationTokenSource is a class that can be used to signal cancellation to any task, thread or other code. It does that by providing a CancellationToken whose IsCancellationRequested property becomes true when someone calls Cancel() on the CTS, or its timeout interval expires.

This way, you can provide a timeout feature to your block by creating a CTS with a timeout period :

var cts=new CancellationTokenSource(60000); //Timeout in 1 minute
var options = new ExecutionDataflowBlockOptions
     {
        MaxDegreeOfParallelism = 20,
        CancellationToken=cts.Token
     };

var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file), options);

//.....

try
{
    await block.Completion;
}
catch (OperationCanceledException)
{
    Console.WriteLine("Timed out!");
}

A button event can be used to signal cancellation if the CTS is stored in a field : CancellationTokenSource _cts; ActionBlock _block;

public void Start_Click(object sender, EventArgs args)
{
    //Make sure both the CTS and block are created before setting the fields  
    var cts=new CancellationTokenSource(60000); //Timeout in 1 minute
    var token=cts.Token;

    var options = new ExecutionDataflowBlockOptions
    {
        MaxDegreeOfParallelism = 20,
        CancellationToken=token
     };
   var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file,token), 
                                     options);
   //Once preparation is over ...
   _cts=cts;
   _block=block;

   //Start posting files
  ...

}

public async void Cancel_Click(object sender, EventArgs args)
{
   lblStatus.Text = "Cancelling";
    _cts.Cancel();

   try
   {
       await _block.Completion;           
   }
   lblStatus.Text = "Cancelled!";
}

Loading large files with cancellation

Asynchronous file operations also accept a cancellation token, eg FileStream.ReadAsync has an overload that accepts a CancellationToken

This means that the worker method can be cancelled if it's converted to an async method, eg

async Task MySlowMethod(string fileName,CancellationToken token)
{
    try 
    {
        using (FileStream SourceStream = File.Open(filename, FileMode.Open))
        {
            var data = new byte[SourceStream.Length];
            await SourceStream.ReadAsync(data, 0, (int)SourceStream.Length,token);
            // Use the data
        }
}

Upvotes: 1

Related Questions