2

I have a problem with backgroundworkers. I have a trackbar, and when the user changes its value, then a new backgroundworker starts. There is a list of all backgroundworkers, and when a new one is started, all workers in the list call worker.CancelAsync().

It works when a user does slow changes on the trackbar, but when you moves it very fast, there are about 20+ threads and it takes some time to kill them in WorkerCompleted. Also, in this function worker variables are cleaning (in this case this is a copy of bitmap), so 20+ workers needs a lot of memory and I get an OutOfMemoryException.

Is there any way to block number of threads to about 4, and when the number of backgroundworkers is equal to 4 then program will wait when they will be deleted, or is there any way to do this with only one backgroundworker, and when the trackbar value is changed it is restarted?


Adding new worker:

public override void StartWorker(Bitmap bmp, bool needTempImage)
{
    if(m_workersList.Count<maxThread)
    {
        CancelAllJobs();

        // debug.Text = "locked";
        BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);

        imageDataAttributes imgAttr = new imageDataAttributes(bd.Width, bd.Height, bd.Stride, 4);

        ThreadWorker worker = new ThreadWorker(needTempImage, bd.Scan0, imgAttr);
        bmp.UnlockBits(bd);

        m_workersList.Add(worker);
        m_currentWorker = worker;
        worker.worker.WorkerSupportsCancellation = true;
        worker.worker.DoWork += WorkerDoWork;
        worker.worker.WorkerReportsProgress = report;

        if (report == true)
        {
            worker.worker.ProgressChanged += WorkerProgress;
            m_progressBar.Visible = true;
        }

        worker.worker.RunWorkerCompleted += WorkerCompleted;
        worker.worker.RunWorkerAsync(worker);

        debug.Text = "" + m_workersList.Count;
    }
    //debug.Text = "unlocked";    
}

This is cancelling:

public override void CancelAllJobs()
{
    foreach (ThreadWorker worker in m_workersList)
    {
        worker.cancelled = true;
        worker.worker.CancelAsync();
    }
    debug.Text = "" + m_workersList.Count;
}

Do work:

protected override void WorkerDoWork(object sender, DoWorkEventArgs e)
{
    ThreadWorker worker = (ThreadWorker)e.Argument;
    if (worker.worker.CancellationPending == true)
    {
        e.Cancel = true;
        worker.cancelled = true;
        return;
    }

    WorkerProcessFun(worker);
}

WorkerCompleted:

protected override void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    ThreadWorker worker = m_workersList.Find(w => w.worker == sender);

    if (!worker.cancelled && worker == m_currentWorker)
    {
        if (e.Error != null)
        {
            MessageBox.Show("Worker Thread Error " + e.Error, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        else
        {
            WorkerOnCompleteFun(worker.imgDataArray);
        }
    }

    m_workersList.Remove(worker);
    worker.Clean();
    if (worker == m_currentWorker) m_currentWorker = null;
    debug.Text = "" + m_workersList.Count;
}

ProcessMainFun needs worker, becouse there is checking CancelationPending, setting e.Cancel=true; and return;

private void MainProcessFun(ThreadWorker worker)
{
    Filters.Filters.AdvancedBlur(m_radius, m_sigma, worker);
}
Mo Patel
  • 2,321
  • 4
  • 22
  • 37
  • You must show the what you have currently tried in code or you risk your question being closed. – Scott Chamberlain Nov 19 '13 at 21:40
  • if background worker isn't enough, you might need to use the threadpool. Maybe have your own collections of threads that you manage by yourself ... – Noctis Nov 19 '13 at 21:44
  • There's no scenario where your test on CancellationPending inside the DoWork method ever will be true, you are testing it a microsecond after you called DoWork. There's no scenario where "ThreadWorker" could ever know that BackgroundWorker.CancellationPending is ever true. So this can't work, kaboom. Get ahead by not starting the BGW until some time has elapsed or adding an DoIt button. – Hans Passant Nov 19 '13 at 23:36

1 Answers1

0

This code-behind for a button and a label describes how you can start a single background thread and then restart it at an arbitrary position using a new starting state.

In this case I choose to send an integer but you can easily send a very complex object instead. Replace the code in CodeToRunInsideBackgroundThread(object state) with any code you need... The point is you don't need multiple threads.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        Thread backgroundWorker = null;
        int startingThreadState = 0;

        private void button1_Click(object sender, EventArgs e)
        {
            startingThreadState += 100;
            if (backgroundWorker == null || !backgroundWorker.IsAlive)
            {
                InitThread();
                backgroundWorker.Start(startingThreadState);
            }
            else
            {
                backgroundWorker.Abort(startingThreadState);
            }
        }

        private void InitThread()
        {
            backgroundWorker = new Thread(new ParameterizedThreadStart((state)=>
                {
                    while (true)
                    {
                        try
                        {
                            CodeToRunInsideBackgroundThread(state);
                            break;//while(true)
                        }
                        catch (ThreadAbortException ex)
                        {
                            System.Threading.Thread.ResetAbort();
                            state = startingThreadState;// state available in ex.Data here?
                        }
                    }
                }));
            backgroundWorker.IsBackground = true;
        }

        private void CodeToRunInsideBackgroundThread(object state)
        {
            for (int i = (int)state; i < (int)state + 3; i++)
            {
                System.Threading.Thread.Sleep(1000);
                this.Invoke(
                    new Action(() =>
                    {
                        label1.Text = i.ToString();
                    })
                );
            }
        }
    }
}
Mattias Åslund
  • 3,877
  • 2
  • 18
  • 17
  • I will try do write my code using Thread instead of BackgroundWorker, becouse Thread can be killed with Abort and i think this would help me. Bgworker has only CancelAsync which is no killing thread immediately i think. – Smajler Kamil Nov 19 '13 at 22:53