Documentation

Best place to hear about BTM implementation details

All example's sources are placed in VS 2010 Project folder.
Compiled version of examples - in Compiled/BasicThreadManagerSampleApp.exe.

All examples screen

Curiosity

Above window is automatically generated from WinAPI called TaskDialog. Check Program.cs file for details

Simple multithreaded iteration picture 1

Above image presents very similar window to window from Getting started section, but with additional features like:

  • Autoupdating window title. Check WindowTitle property
  • Abortable task with prompt - if you click close button in progressbar window you will see prompt with question - as you can see in the image below. Check ThreadWorkerWindowClosed event

Simple multithreaded iteration picture 2

If you choose Cancel button (sorry for polish translation) or like an image Anuluj this task will be cancelled. Before that, task is paused and waiting for your move.

If you click minimize button, BTM will be hidden into tray. Check View.Trayable property

Simple multithreaded iteration picture hide to tray

You can execute below code to achieve this effect.

Comments

All comments and reviews are placed in code after // (double slash) or multiline comments /* ... */

/*
 * Source from Program.cs file
 * Subproject: BasicThreadManagerSampleApp
 * Start line: 70
 */
 
var sgt = new BasicThreadWorker();
sgt.Maximum = 100000;               // Set maximum for counting
sgt.View.Trayable = true;           // Is BTM hides to tray after minimize window ?
sgt.UseTaskbarProgressBar = true;   // Is BTM show progress also in taskbar ?

sgt.ThreadMethod = delegate
{
    for (int i = 0; i < sgt.Maximum; i++)
    {
        // Our processor is too fast to see what is going on the screen so 
        // we have to delay current thread about 1ms.
        System.Threading.Thread.Sleep(1);
        
        // If BTM is paused, loop start working... 
        while (sgt.isPaused)
        {
            System.Threading.Thread.Sleep(100);
        }
        
        // Reporting progress to BTM is not mandatory, but if we have opportunity 
        // to show progress in BTM window - it is.
        sgt.ReportProgress(i);
        
        // That's we updating BTM window title...
        sgt.WindowTitle = string.Format("Loading status {0}", i);
    }
};

// This is one of many BTM's events - When you try to close BTM...
sgt.ThreadWorkerWindowClosed += delegate(object o, ThreadWorkerWindowEventArgs args)
{
    // Check if BTM is working while we clicking Closing button, if so...
    if (sgt.IsBusy)
    {
        // If paused already then unpause
        if (sgt.isPaused)
        {
            sgt.Resume();
        }
        else
        {
            // Send pause signal to BTM - see line 21
            sgt.Pause();
            switch (
                MessageBox.Show("You can continue this process by clicking Retry button or press Abort to cancel.",
                                "Process has been stopped",
                                MessageBoxButtons.RetryCancel,
                                MessageBoxIcon.Question))
            {
                case DialogResult.Retry:
                    sgt.Resume(); // Resume BTM working
                    break;
                case DialogResult.Cancel:
                    sgt.Cancel(); // Cancel BTM working
                    break;
            }
        }
    }
};

// At the end most important thing - Start BTM :)
sgt.Start();

You can also show task completition in taskbar icon (WinAPI). Check UseTaskbarProgressBar property

Taskbar progress

You can execute below code to achieve this effect.

Comments

All comments and reviews are placed in code after // (double slash) or multiline comments /* ... */

/*
 * Source from Program.cs file
 * Subproject: BasicThreadManagerSampleApp
 * Start line: 123
 */

var sgt = new BasicThreadWorker();
sgt.Maximum = 500;                              // Set maximum for counting
sgt.UseTaskbarProgressBar = true;               // Is BTM show progress also in taskbar ?

// Before use it, add reference to Microsoft.WindowsAPICodePack.Shell.dll in project!
sgt.TaskbarProgressBarState = TaskbarProgressBarState.Error;

sgt.ThreadMethod = delegate
{
    for (int i = 0; i < sgt.Maximum; i++)
    {
        System.Threading.Thread.Sleep(5);
        
        // Reporting progress to BTM is not mandatory, but if we have opportunity 
        // to show progress in taskbar - it is.
        sgt.ReportProgress(i);
    }
};

// At the end most important thing - Start BTM :)
sgt.StartSimple();
                    

For more Taskbar colors and effects check TaskbarProgressBarState property

BTM might be used also for processing time-undefined task such as downloading remote file (http/ftp/etc.). Click one of this three links or enter your own remote file location.

File downloader 1

After entering remote file location and click Down them all! button we have Preparing to download title...

File downloader 2

...after 2 or 3 seconds later download begins...

File downloader 3

You can execute below code to achieve this effect.

Comments

All comments and reviews are placed in code after // (double slash) or multiline comments /* ... */

/*
 * Source from FileDownloader.cs file
 * Subproject: BasicThreadManagerSampleApp/Views
 * Start line: 22
 */

// If remote file location address is empty cancel download...
if (url.Text == string.Empty) return;

var sgt = new BasicThreadWorker();
sgt.Maximum = 100;                              // Set maximum for counting
sgt.PreloaderSpeed = 5;                         // Set default marquee speed
sgt.WindowTitle = "Preparing to download...";   // Set initial window title
sgt.View.Trayable = true;                       // Is BTM hides to tray after minimize window ?
sgt.UseTaskbarProgressBar = true;               // Is BTM show progress also in taskbar ?

var sfd = new SaveFileDialog();                 // Display Save Dialog window

var ft = Path.GetExtension(url.Text);

sfd.Filter = string.Format("File {0} ({1})|{1}|All Files (*.*)|*.*", ft.ToUpper(), ft);
sfd.Title = "Save remote file on local disk";
sfd.ShowDialog();
if (sfd.FileName != string.Empty)
{
    //sgt.Maximum = 100;
    sgt.ThreadMethod = delegate
    {
        var remoteFilename = string.Empty;
        
        // This is very important in all of multithreading stuff!
        // If you want to modify form control such as button, textbox etc.. you need do it with .Invoke
        // Without that this code fails cause of thread crossing problem. Check description under this code...
        url.Invoke(
                (MethodInvoker)delegate
                {
                    remoteFilename = url.Text;
                });
        
        // This is file downloading sections...
        // First check size of remote file, secondly download it...
        var req = WebRequest.Create(remoteFilename);
        req.Method = "HEAD";
        using (WebResponse resp = req.GetResponse())
        {
            int contentLength;
            if (int.TryParse(resp.Headers.Get("Content-Length"), out contentLength))
            {
                // Set progressbar style
                sgt.ProgressBarStyle = ProgressBarStyle.Continuous;
                
                // Set BTM maximum to filesize in bytes
                sgt.Maximum = contentLength;

                int bytesProcessed = 0;

                Stream remoteStream = null;
                Stream localStream = null;
                WebResponse response = null;

                try
                {
                    var request = WebRequest.Create(remoteFilename);
                    response = request.GetResponse();
                    remoteStream = response.GetResponseStream();
                    localStream = File.Create(sfd.FileName);

                    byte[] buffer = new byte[1024];
                    int bytesRead = 0;

                    do
                    {
                        if (remoteStream != null) bytesRead = remoteStream.Read(buffer, 0, buffer.Length);
                        localStream.Write(buffer, 0, bytesRead);
                        bytesProcessed += bytesRead;

                        if (remoteStream != null)
                        {
                            // Report progress... How many bytes are downloaded
                            sgt.ReportProgress(bytesProcessed);
                            
                            // Autoupdate BTM window title
                            sgt.WindowTitle = string.Format("{0}% - {1}", Math.Round(double.Parse((bytesProcessed*100/contentLength).ToString(CultureInfo.InvariantCulture)), 2), url.Text);
                        }
                    }
                    while (bytesRead > 0);
                }
                catch (Exception er)
                {
                    Console.WriteLine(er.Message);
                }
                finally
                {
                    // Close all of opened resources...
                    if (response != null) response.Close();
                    if (remoteStream != null) remoteStream.Close();
                    if (localStream != null) localStream.Close();
                }
            }
        }
    };

    #region = ThreadWorkerWindowClosed =
    sgt.ThreadWorkerWindowClosed += delegate(object o, ThreadWorkerWindowEventArgs args)
    {
        // Check if BTM is working while we clicking Closing button, if so...
        if (sgt.IsBusy)
        {
            // If paused already then unpause
            if (sgt.isPaused)
            {
                sgt.Resume();
            }
            else
            {
                // Send pause signal to BTM
                sgt.Pause();
                switch (
                    MessageBox.Show("You can continue this process by clicking Retry button or press Abort to cancel.",
                                    "Process has been stopped",
                                    MessageBoxButtons.RetryCancel,
                                    MessageBoxIcon.Question))
                {
                    case DialogResult.Retry:
                        sgt.Resume();
                        break;
                    case DialogResult.Cancel:
                        sgt.Cancel();
                        break;
                }
            }
        }
    }; 
    #endregion

    // At the end most important thing - Start BTM :)
    sgt.Start();

Must see

If you have trouble with cross-threads, be sure you read section How to update GUI ?

This sample app shows how to parse and search through large files (CSV).

After lunching this app, BTM loads CSV file into memory (RAM) for further use by another methods. This process ends very fast. Probably you won't see anything but trust me - BTM works there.

After that you will see this window...

Students finder 2000 1

CSV file (sample) looks as shown below - Name, Surname and E-mail

Students finder 2000 5

If you type some text (name, surname or e-mail, regular expressions also) into this field and press Enter app will alive...

Students finder 2000 2

Whole searching process is showed in small progressbar on the right of this window, also we updating groupbox Searching status about new matches

If you click Show me all Students button you will se BTM window and do some new students parsing. After this process BTM display new window with ListView control which is filled by parsed students data

Students finder 2000 3

You can execute below code to achieve this effect.

Comments

All comments and reviews are placed in code after // (double slash) or multiline comments /* ... */

/*
 * Source from PersonalFinder.cs file
 * Subproject: BasicThreadManagerSampleApp/Views
 * Start line: 107
 */

loader_pgbar.Maximum = _personalData.Length;        // Set maximum of mini-progressbar
loader_pgbar.Style = ProgressBarStyle.Blocks;       // Set look of mini-progressbar

var sgt = new BasicThreadWorker();
sgt.Maximum = _personalData.Length;                 // Set maximum of BTM

sgt.ThreadMethod = delegate
{
    listView1.Invoke((MethodInvoker)delegate()
    {
        listView1.Items.Clear();                    // Thread-safely clear previous results
    });

    var i = 0;
    var matches_cnt = 0;

    foreach (string student in _personalData)
    {
        sgt.ReportProgress(i++);                    // Rise progress status up

        loader_pgbar.Invoke((MethodInvoker)delegate()
        {
            loader_pgbar.Value = i;
        });
        
        // Change label above mini-progressbar
        loader_lbl.Invoke((MethodInvoker)delegate()
                                                {
                                                    loader_lbl.Text =
                                                        string.Format("Searching... {0}%", Math.Round((double.Parse((i*100/_personalData.Length).ToString(CultureInfo.InvariantCulture))), 2));
                                                });

        try
        {
            // Check by regular expression
            Match match = Regex.Match(student, textBox1.Text);
            if (match.Success)
            {
                label3.Invoke((MethodInvoker)delegate()
                {
                    label3.ForeColor = Color.Green;
                });

                matches_cnt++;
                
                // Thread-safely change title of groupbox
                groupBox1.Invoke((MethodInvoker)delegate()
                                                     {
                                                         groupBox1.Text =
                                                             string.Format("Searching status - {0} matches", matches_cnt);
                                                     });

                try
                {
                    string[] student_data = student.Split(';');
                    var lvi = new ListViewItem();
                    lvi.Text = student_data[0];
                    lvi.SubItems.Add(student_data[1]);
                    lvi.SubItems.Add(student_data[2]);

                    listView1.Invoke((MethodInvoker)delegate()
                    {
                        listView1.Items.Add(lvi);               // Thread-safely add new ListView item
                    });
                }
                catch (Exception err)
                {
                    Console.WriteLine("Parsing error " +
                                        student +
                                        Environment.NewLine +
                                        "Err: " + err.Message);
                }
            }
            else
            {
                label3.Invoke((MethodInvoker)delegate()
                {
                    label3.ForeColor = Color.Red;
                });
            }
        }
        catch (Exception err)
        {
            MessageBox.Show(err.Message, "An error has occured", MessageBoxButtons.OK, MessageBoxIcon.Stop);
            return;
        }

        label3.Invoke((MethodInvoker)delegate()
        {
            label3.Text = student;
        });

        listView1.Invoke((MethodInvoker)delegate()
        {
            if(listView1.Items.Count > 2) listView1.Items[listView1.Items.Count - 1].EnsureVisible();
        });
    }
};

// After BTM starts...
sgt.ThreadWorkerStart += delegate(object sender, ThreadWorkerEventArgs args)
{
    ShowLoader();
};

// After BTM ends
sgt.ThreadWorkerEnd += delegate(object sender, ThreadWorkerEventArgs args)
{
    HideLoader();
};

// Start BTM with non-windowed state
sgt.StartSimple();

Many times you need to chain execution of your methods in particular order - trust me

The solution is Queue. Check below code.

/*
 * Source from Program.cs file
 * Subproject: BasicThreadManagerSampleApp
 * Start line: 153
 */
 
var queue = new BasicThreadQueue();
queue.Add(ActionTest1);
queue.Add(ActionTest2);
queue.Add(ActionTest3);
queue.Run();

Methods ActionTest1 to ActionTest3 have to be written in BTM style, eg.

private static void ActionTest1()
{
    var sgt = new BasicThreadWorker();
    sgt.Maximum = 1000;
    sgt.UseTaskbarProgressBar = true;
    sgt.Position = FormStartPosition.CenterScreen;
    
    // Before use it, add reference to Microsoft.WindowsAPICodePack.Shell.dll in project!
    sgt.TaskbarProgressBarState = TaskbarProgressBarState.Error;
    
    sgt.ThreadMethod = delegate
    {
        for (int i = 0; i < sgt.Maximum; i++)
        {
            System.Threading.Thread.Sleep(5);
    
            sgt.ReportProgress(i);
            sgt.WindowTitle = string.Format("Downloading the Internet to 3.5\" DDR - {0}", i);
        }
    };
    
    sgt.Start();
}

This app shows you how to change many BTM propeties such as Progressbar color, style and so from while executing task

Check this code.

/*
 * Source from Program.cs file
 * Subproject: BasicThreadManagerSampleApp
 * Start line: 161
 */
 
var sgt = new BasicThreadWorker();
sgt.Maximum = 5000;
sgt.UseTaskbarProgressBar = true;
sgt.Position = FormStartPosition.CenterScreen;

// Before use it, add reference to Microsoft.WindowsAPICodePack.Shell.dll in project!
sgt.TaskbarProgressBarState = TaskbarProgressBarState.Normal;

sgt.ThreadMethod = delegate
{
    for (int i = 0; i < sgt.Maximum; i++)
    {
        sgt.ReportProgress(i);

        if (i >= 1000 && i < 2000)
        {
            System.Threading.Thread.Sleep(5);
            sgt.WindowTitle = string.Format("Parsing file {0}.dll", i.ToString(CultureInfo.InvariantCulture).Md5().ToLower().Substring(0, 15));
            sgt.ProgressBarStyle = ProgressBarStyle.Marquee;
        }
        else if (i >= 2000 && i < 4000)
        {
            System.Threading.Thread.Sleep(5);
            sgt.WindowTitle = string.Format("Installing filesystem... {0}%", sgt.View.prgBar.Value);
            sgt.ProgressBarStyle = ProgressBarStyle.Continuous;
        }
        else if (i >= 4000 && i < 5000)
        {
            System.Threading.Thread.Sleep(15);
            sgt.WindowTitle = string.Format("Compiling & installing file {0}.exe", i.ToString(CultureInfo.InvariantCulture).Md5().ToLower().Substring(0, 15));
            sgt.ProgressBarStyle = ProgressBarStyle.Marquee;
        }
        else
        {
            System.Threading.Thread.Sleep(5);
            sgt.WindowTitle = string.Format("Downloading {0}%", sgt.View.prgBar.Value);
            sgt.ProgressBarStyle = ProgressBarStyle.Blocks;
        }
    }
};

sgt.Start();

This is the most cool and big part of BTM sample app project. In this app you will able to build your own BTM with 100% of your look and feel. On the other hand you will check event names that you want to sing in and check when it's executed

Code of this app is located in BasicThreadManagerSampleApp/Views/Events.cs file.

This app look like this

Events preview 1

Events preview 2

Have you ever seen this guy ?

Cross Thread Exception

If so, you must read this section to get rid of exception

What is he talking about...

When making method calls to a control, if the caller is on a different thread than the one the control was created on, you need to call using Control.Invoke.

Solution is very simple. Every time when we need to modify eg. button from another thread like

// This section creates new thread...
sgt.ThreadMethod = delegate
{
    // THIS IS VERY BAD IDEA CAUSES EXCEPTION!
    mybutton.Text = "some title";
    
    // Between this parenthasis { and } you have to call by .Invoke()
    // How to do this ? - scroll down...     
};

...we need to use .Invoke

// This section creates new thread...
sgt.ThreadMethod = delegate
{
    mybutton.Invoke(
        (MethodInvoker)delegate
        {
            mybutton.Text = "some title";
        });
};

If you scared... - don't be! If you don't understand all of it - it's ok!

Most important thing is to use it in this way. I wrote code template. You will only have to apply it to your project. Check this out.

// This section creates new thread...
sgt.ThreadMethod = delegate
{
    <control_name>.Invoke(
        (MethodInvoker)delegate
        {
            // Now do whatever you want with <control_name> !
        });
};

Ofcourse, when you modyfing control created in the same thread eg.

private void button1_Click(object sender, EventArgs e)
{
    // Nice!, you do not have to use .Invoke()
    mybutton.Text = "some title";
}

There is no need to use .Invoke()

If we have situation:

  1. We have method button1_Click() like above
  2. We have another method called changeButtonText()
  3. From button1_Click() method we lunching changeButtonText() method
  4. From BTM sgt.ThreadMethod = delegate { changeButtonText(); }; we lunching changeButtonText() method

private void button1_Click(object sender, EventArgs e)
{
    // This is called from the same thread with application...
    changeButtonText();
}

// ...creating BTM...
sgt.ThreadMethod = delegate
{
    // This is called from another thread...
    changeButtonText();
};
sgt.Start();

private void changeButtonText()
{
    mybutton.Invoke(
        (MethodInvoker)delegate
        {
            mybutton.Text = "some title";
        });
}

You will see exception... why ? It's simple. If you have one method which is working for two another methods (one - from the same thread, another - from new thread) you must check if .Invoke() is required for example it's not if you calling this method from the same thread.

Above code needs to be

private void button1_Click(object sender, EventArgs e)
{
    // This is called from the same thread with application...
    changeButtonText();
}

// ...creating BTM...
sgt.ThreadMethod = delegate
{
    // This is called from another thread...
    changeButtonText();
};
sgt.Start();

private void changeButtonText()
{
    // Check if .Invoke() is required
    if (mybutton.InvokeRequired)
    {
        mybutton.Invoke(
            (MethodInvoker)delegate
            {
                mybutton.Text = "some title";
            });
    }
    else
    {
        mybutton.Text = "some title";
    }
}

And that's all!

Name Description Default value
public prop IsBusy Get information about whether BTM is working or not false;
public prop IsPaused Get information about whether BTM is paused or not false;
public prop Maximum Get or Set maximum value for progressbar 0;
public prop Minimum Get or Set minimum value for progressbar 0;
public prop Position Get or Set value of BTM window position relative to screen. Available values
  • CenterParent
  • CenterScreen
  • Manual
  • WindowsDefaultBounds
  • WindowsDefaultLocation
FormStartPosition.CenterScreen;
public prop PreloaderSpeed Get or Set value of progressbar's marquee animation speed 5;
public prop ProgressBarStyle Get or Set style of BTM window progressbar ProgressBarStyle.Blocks;
public prop TaskbarProgressBarState Get or Set style of progressbar display in taskbar icon TaskbarProgressBarState.NoProgress;
public prop ThreadMethod Get or Set method for futher executing in another thread NULL
public prop ThreadName Get or Set name of thread created by ThreadMethod NULL
public prop UseTaskbarProgressBar Get or Set value whether progressbar will be displayed in taskbar icon false;
public prop View Get or Set BTM window form with progressbar new PrimaryWnd();
public prop WindowTitle Get or Set BTM window title string.Empty;
public prop View.Trayable Get or Set value meaning is BTM window will minimize to tray after clicking minimize button or not false;
private prop _bw Contains instance of background worker NULL
private prop _isDisposed Contains value indicating whether object removed from memory (disposed) false;
private prop _isPaused Contains value indication whether BTM is paused or not false;
private prop _maxValue Contains maximum value for progressbar 0;
private prop _minValue Contains minimum value for progressbar 0;
private prop _pos Contains value of position BTM window on the screen FormStartPosition.CenterScreen;
private prop _preloaderSpeed Contains preloader's speed value 5;
private prop _taskbarProgressBarState Contains value of progressbar style in taskbar icon TaskbarProgressBarState.NoProgress;
private prop _threadMethod Contains method for futher executing in another thread NULL
private prop _threadName Contains name of thread created by _threadMethod NULL
private prop _useTaskbarProgressBar Contains value whether progressbar will be displayed in taskbar icon false;
private prop _view Contains BTM window form with progressbar new PrimaryWnd();
private prop _windowsTaskbar Contains instance on WinAPI TaskDialog object NULL
private prop _wndProgBarStyle Contains style for BTM window progressbar ProgressBarStyle.Blocks;
private prop _wndTitle Contains actual BTM window title string.Empty;
Name Description Return value
public method BasicThreadWorker(string threadName) Constructor with thread name parameter. If given, newly created threads will be named like threadName value void
public method Cancel() Cancel current task void
public method Dispose Clearing memory and destroy BTM object void
public method OnThreadWorkerCancel(ThreadWorkerEventArgs e) Virtual method connected with ThreadWorkerCancel event. Executed when BTM is cancelled. void
public method OnThreadWorkerEnd(ThreadWorkerEventArgs e) Virtual method connected with ThreadWorkerEnd event. Executed when BTM ends. void
public method OnThreadWorkerError(ThreadWorkerEventArgs e) Virtual method connected with ThreadWorkerError event. Executed when BTM has some errors. void
public method OnThreadWorkerPause(ThreadWorkerEventArgs e) Virtual method connected with ThreadWorkerPause event. Executed when BTM is paused. void
public method OnThreadWorkerStart(ThreadWorkerEventArgs e) Virtual method connected with ThreadWorkerStart event. Executed when BTM starts. void
public method OnThreadWorkerWindowClosed(ThreadWorkerWindowEventArgs e) Virtual method connected with ThreadWorkerWindowClosed event. Executed when BTM window closed. void
public method OnThreadWorkerWindowMinimized(ThreadWorkerWindowEventArgs e) Virtual method connected with ThreadWorkerWindowMinimized event. Executed when BTM minimized (clicked minimize button). void
public method OnThreadWorkerWindowRestored(ThreadWorkerWindowEventArgs e) Virtual method connected with ThreadWorkerWindowRestored event. Executed when BTM window is restored from minimize/tray state. void
public method OnThreadWorkerWindowTrayed(ThreadWorkerWindowEventArgs e) Virtual method connected with ThreadWorkerWindowTrayed event. Executed when BTM window has trayed. void
public method OnThreadWorkerWindowUntrayed(ThreadWorkerWindowEventArgs e) Virtual method connected with ThreadWorkerWindowUntrayed event. Executed when BTM restored from tray. void
public method Pause() Pause current thread execution void
public method ReportProgress(int percent) Gives to BTM information about current status void
public method ReportProgress(int percent, Dictionary<string,string> param) Gives to BTM information about current status and additional params void
public method Resume() When current thread is paused this method unpausing it void
public method Start() Starts BTM with windowed mode void
public method StartSimple() Starts BTM without windowed mode void
Name Description
public event ThreadWorkerCancel Occurs when BTM cancelled
public event ThreadWorkerEnd Occurs when BTM ends
public event ThreadWorkerError Occurs when BTM has some errors
public event ThreadWorkerPause Occurs when BTM paused
public event ThreadWorkerStart Occurs when BTM starts
public event ThreadWorkerWindowClosed Occurs when BTM window closed
public event ThreadWorkerWindowMinimized Occurs when BTM window minimized
public event ThreadWorkerWindowRestored Occurs when BTM window restored from minimize/tray state
public event ThreadWorkerWindowTrayed Occurs when BTM window trayed
public event ThreadWorkerWindowUntrayed Occurs when BTM window restored from tray icon