locked
Can a background worker raise an event that passes a string? RRS feed

  • Question

  • I see that from a background worker you can call "ReportProgress", which fires an event in the caller.  This event expects an integer, which is the percent progress (between 0 and 100).  But suppose, instead of an integer, I want to pass a string to the caller, or a double?  Suppose the number I want to pass doesn't stand for the progress, but just some average of realtime data (double), or a message (string) that a connection was aborted?

    How is that done?  Its not as easy as setting the 'e' argument of DoWork, because in my case DoWork creates events, and the information occurs in the event.  This programming situation sounds odd, but the code is a third party library called from a static class, and I'm using a background worker to operate the class, even though I'm not even clear why I'm doing that.

    The main idea is that when events fire in the class, I would like them to pass data back to a form, without terminating the class operation.  For instance, suppose the class is coping with incoming real time data from a temperature sensor.  I may want it to pass information to the form that the user is looking at, information such as average temperature every minute for example.  I could throw an exception with a string of information, but that would terminate the class process (I think).  Is there a way to pass information to the form that uses the class which meets two criteria

    1. the information happens at unpredictable times - it is dependent on real-time info coming into the computer.

    2. it should not terminate the processing that is done by the class.  

    Thanks.


    Sunday, September 20, 2015 9:24 PM

Answers

  • There are two issues with Multithreading and GUI's:

    1. GUI elements should only ever be created and written by the GUI thread. BGW does most of the heavy lifting for you: ReportProgress and RunWorkerCompleted Events are automatically raised on the GUI thread via Invoke. So just contain your GUI writing code to them.

    Invoke

    2. ReportProgess has a onverride that accepts 1 int and 1 object. String is a class and everything fits into object reference.
    Normally if you hand out class references between threads you can run into issues because 2 threads hold references now. You should thus always properly clone what you hand out.
    String is one of the few exceptions. In thier wisdow the developers of .NET decided to make String an inmutable class (like value types are inmutable). So it is one of the few classes that won't cause any issues if it is passed between threads.

    So yes, you can totally output a string from a BackgroundWorker via report progress. Indeed it is easier then any other class you might run into.
    You should not, however:

    1. BGW is designed for a "return all or nothing" approach. This is generally an adviseable pattern for most multithreading.

    2. Writing strings to the GUI has a considerable overhead. So much so, if Report Progress is jsut fired often enough you can still effortlessly overload the GUI Thread. I stumbeled about this when trying to get data out of a BGW the first time using it. Updating a Progress bar is far less resourse intensive.

    IEnumerables are on of the few cases where this rule is broken. But only because the GUI itself has to poll for the next entry and thus won't be overloaded easily.

    Here is some code to simualte the GUI Write Overhead:

    using System;
    using System.Windows.Forms;
    
    namespace UIWriteOverhead
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            int[] getNumbers(int upperLimit)
            {
                int[] ReturnValue = new int[upperLimit];
    
                for (int i = 0; i < ReturnValue.Length; i++)
                    ReturnValue[i] = i;
    
                return ReturnValue;
            }
    
            void printWithBuffer(int[] Values)
            {
                textBox1.Text = "";
                string buffer = "";
    
                foreach (int Number in Values)
                    buffer += Number.ToString() + Environment.NewLine;
                textBox1.Text = buffer;
            }
    
            void printDirectly(int[] Values){
                textBox1.Text = "";
    
                foreach (int Number in Values)
                    textBox1.Text += Number.ToString() + Environment.NewLine;
            }
    
            private void btnPrintBuffer_Click(object sender, EventArgs e)
            {
                MessageBox.Show("Generating Numbers");
                int[] temp = getNumbers(10000);
                MessageBox.Show("Printing with buffer");
                printWithBuffer(temp);
                MessageBox.Show("Printing done");
            }
    
            private void btnPrintDirect_Click(object sender, EventArgs e)
            {
                MessageBox.Show("Generating Numbers");
                int[] temp = getNumbers(1000);
                MessageBox.Show("Printing directly");
                printDirectly(temp);
                MessageBox.Show("Printing done");
            }
        }
    }

    • Proposed as answer by DotNet Wang Thursday, September 24, 2015 6:27 AM
    • Marked as answer by Kristin Xie Tuesday, September 29, 2015 8:53 AM
    Monday, September 21, 2015 2:15 AM