Simply create a tutorial with multiple threads using BackgroundWorker

  • 2020-05-27 04:44:35
  • OfStack

BackgroundWorker is a very good thread control, can avoid interface feign death, let the thread operate what you want to do, it is very easy to learn, but can achieve very powerful functions. Release the purpose of this article is to study recently to share, to communicate one, of course I am a rookie, here you will learn to BackgroundWorker simple use, stop, pause, to continue operations, such as BackgroundWorker than Thread and ThreadPool is too much simple, in order to more convenient to use in the practical application, what I use is winform, did not use the console application.

Drag an button and richTextBox to the UI interface.

I will start with the simplest code, only the simplest code will make people have the desire to continue to learn, the following code can be printed 1 to 999 to richTextBox1 control.


private void button1_Click(object sender, EventArgs e)
 {
     // create 1 a BackgroundWorker thread 
     BackgroundWorker bw = new BackgroundWorker();
     // create 1 a DoWork Event, specifying bw_DoWork Ways to do things 
     bw.DoWork += new DoWorkEventHandler(bw_DoWork);
     // Start to perform 
     bw.RunWorkerAsync();
 }

 void bw_DoWork(object sender, DoWorkEventArgs e)
 {
     for (int i = 0; i < 1000; i++)
     {
         this.richTextBox1.Text += i + Environment.NewLine;
     }
 }

Unfortunately, the above code will report an error, the error message: the operation between threads is invalid: it is accessed by the thread that never created the control "richTextBox1".

So let's go ahead and tweak the code so that the Numbers are displayed on the richTextBox1 control and the richTextBox1 focus is at the low end.


private void button1_Click(object sender, EventArgs e)
 {
     // create 1 a BackgroundWorker thread 
     BackgroundWorker bw = new BackgroundWorker();
     // create 1 a DoWork Event, specifying bw_DoWork Ways to do things 
     bw.DoWork += new DoWorkEventHandler(bw_DoWork);
     // Start to perform 
     bw.RunWorkerAsync();
 }

 void bw_DoWork(object sender, DoWorkEventArgs e)
 {
     for (int i = 0; i < 1000; i++)
     {
         this.Invoke((MethodInvoker)delegate
         {
             this.richTextBox1.Text += i + Environment.NewLine;
         });
     }
 }

 private void richTextBox1_TextChanged(object sender, EventArgs e)
 {
     RichTextBox textbox = (RichTextBox)sender;

     textbox.SelectionStart = textbox.Text.Length;
     textbox.ScrollToCaret();
 }

Here is the simplest example of BackgroundWorker1, no more complicated code, this is BackgroundWorker, now let's add a stop button to stop the thread.

Drag another button control to the interface to stop the thread. First, we need to modify the code so that the button event can be controlled by the BackgroundWorker thread.


BackgroundWorker bw = null;

 private void button1_Click(object sender, EventArgs e)
 {
     // create 1 a BackgroundWorker thread 
     bw = new BackgroundWorker();
     // Specifies that threads can be stopped 
     bw.WorkerSupportsCancellation = true;
     // create 1 a DoWork Event, specifying bw_DoWork Ways to do things 
     bw.DoWork += new DoWorkEventHandler(bw_DoWork);
     // Start to perform 
     bw.RunWorkerAsync();
 }

 private void button2_Click(object sender, EventArgs e)
 {
     // Stop the thread 
     bw.CancelAsync();
 }

 void bw_DoWork(object sender, DoWorkEventArgs e)
 {
     for (int i = 0; i < 1000; i++)
     {
         // Gets whether the current thread gets an instruction to stop 
         if (bw.CancellationPending)
         {
             e.Cancel = true;
             return;
         }

         this.Invoke((MethodInvoker)delegate
         {
             this.richTextBox1.Text += i + Environment.NewLine;
         });
     }
 }

In order to avoid the complexity of the code, I did not make more changes to the above code. For example, click the start button, and the start button should be unavailable. Click the stop button, and then the stop button will be unavailable and the start button will be activated.

Now we will continue to upgrade. How do we know if the thread has finished executing or if the thread has stopped


BackgroundWorker bw = null;

 private void button1_Click(object sender, EventArgs e)
 {
     bw = new BackgroundWorker();
     bw.WorkerSupportsCancellation = true;
     bw.DoWork += new DoWorkEventHandler(bw_DoWork);
     // The thread completes or stops the events that occur 
     bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

     bw.RunWorkerAsync();
 }

 private void button2_Click(object sender, EventArgs e)
 {
     bw.CancelAsync();
 }

 void bw_DoWork(object sender, DoWorkEventArgs e)
 {
     for (int i = 0; i < 1000; i++)
     {
         if (bw.CancellationPending)
         {
             e.Cancel = true;
             return;
         }

         this.Invoke((MethodInvoker)delegate
         {
             this.richTextBox1.Text += i + Environment.NewLine;
         });
     }
 }

 void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 {
     if (e.Cancelled)
     {
         this.richTextBox1.Text += " The thread has stopped ";
     }
     else
     {
         this.richTextBox1.Text += " The thread is done ";
     }
 }

Now you can create a thread by yourself using BackgroundWorker, you already know about it, and of course BackgroundWorker also has an ReportProgress scroll bar event that shows progress, which I temporarily think is redundant, because most progress can be controlled by bw_DoWork. Let's continue to improve BackgroundWorker by adding pause and continue functions.

Drag another button control to the interface, BackgroundWorker pause and continue we use ManualResetEvent.


BackgroundWorker bw = null;
 // create ManualResetEvent
 ManualResetEvent mr = new ManualResetEvent(true);  

 private void button1_Click(object sender, EventArgs e)
 {
     bw = new BackgroundWorker();
     bw.WorkerSupportsCancellation = true;
     bw.DoWork += new DoWorkEventHandler(bw_DoWork);
     bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

     bw.RunWorkerAsync();
 }

 private void button2_Click(object sender, EventArgs e)
 {
     bw.CancelAsync();
 }

 private void button3_Click(object sender, EventArgs e)
 {
     Button b = (Button)sender;
     if (b.Text == " suspended ")   
     {   
         mr.Reset();
         b.Text = " Continue to ";   
     }   
     else  
     {   
         mr.Set();   
         b.Text = " suspended ";   
     }  

 }

 void bw_DoWork(object sender, DoWorkEventArgs e)
 {
     for (int i = 0; i < 1000; i++)
     {
         if (bw.CancellationPending)
         {
             e.Cancel = true;
             return;
         }

         this.Invoke((MethodInvoker)delegate
         {
             this.richTextBox1.Text += i + Environment.NewLine;
         });

         // Accept the work instruction 
         mr.WaitOne();
     }
 }

 void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 {
     if (e.Cancelled)
     {
         this.richTextBox1.Text += " The thread has stopped ";
     }
     else
     {
         this.richTextBox1.Text += " The thread is done ";
     }
 }

Most of BackgroundWorker's functionality has been implemented so far, and the above code can be found in many blogs, all with only one background thread executed. If we have 1000 time-consuming tasks, then 1 thread is not enough, we need to create multiple threads, let's say 10 threads, divide the 1000 tasks into different equal parts and let the 10 threads execute separately.

We use the list generic List < BackgroundWorker > , bw.RunWorkerAsync (i) to bw_DoWork, and e.Argument to bw_DoWork.


List<BackgroundWorker> bws = new List<BackgroundWorker>();
 int t = 10;

 private void button1_Click(object sender, EventArgs e)
 {
     for (int i = 0; i < t; i++)
     {
         BackgroundWorker bw = new BackgroundWorker();
         bw.DoWork += new DoWorkEventHandler(bw_DoWork);
         bws.Add(bw);

         bw.RunWorkerAsync(i);
     }
 }

 void bw_DoWork(object sender, DoWorkEventArgs e)
 {
     int j = Convert.ToInt32(e.Argument); 
     for (int i = j; i < 1000; i = i + t) 
     {
         if (((BackgroundWorker)sender).CancellationPending)  
         {
             e.Cancel = true;
             return;
         }

         string item = String.Format(" thread {0} Manipulating data {1}", j + 1, i);

         this.Invoke((MethodInvoker)delegate
         {
             this.richTextBox1.Text += item + Environment.NewLine;
         });

         //Thread.Sleep(200);
     }
 }

Since the above code is not time-consuming operation, and 10 more threads are started, the operation is too fast, resulting in the interface in a state of suspended animation. You can use Sleep to put the threads to sleep.

We continue to improve the code, add stop operation, add the event after the completion and stop, because it is multi-threaded, judge whether the thread operation is completed, we use bws.Remove (sender as BackgroundWorker); Method to delete the thread and then use bws.Count == 0 to determine if the operation is complete.


List<BackgroundWorker> bws = new List<BackgroundWorker>();
 int t = 10;

 private void button1_Click(object sender, EventArgs e)
 {
     for (int i = 0; i < t; i++)
     {
         BackgroundWorker bw = new BackgroundWorker();
         bw.DoWork += new DoWorkEventHandler(bw_DoWork);
         bw.WorkerSupportsCancellation = true;
         bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
         bws.Add(bw);

         bw.RunWorkerAsync(i);
     }
 }

 private void button2_Click(object sender, EventArgs e)
 {
     for (int i = 0; i < t; i++)
     {
         bws[i].CancelAsync();
     }
 }

 void bw_DoWork(object sender, DoWorkEventArgs e)
 {
     int j = Convert.ToInt32(e.Argument); 
     for (int i = j; i < 1000; i = i + t) 
     {
         if (((BackgroundWorker)sender).CancellationPending)  
         {
             e.Cancel = true;
             return;
         }

         string item = String.Format(" thread {0} Manipulating data {1}", j + 1, i);

         this.Invoke((MethodInvoker)delegate
         {
             this.richTextBox1.Text += item + Environment.NewLine;
         });

         Thread.Sleep(200);
     }
 }

 void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 {
     bws.Remove(sender as BackgroundWorker);
     if (bws.Count == 0)
     {
         if (e.Cancelled)
         {
             this.richTextBox1.Text += " The thread has stopped ";
         }
         else
         {
             this.richTextBox1.Text += " The thread is done ";
         }
     }
 }

The stop in the above code does not stop immediately, which is similar to driving 1. The faster you drive, the longer the backdrag distance of the brake. Similarly, the more threads you open, the longer it will take to stop completely. In addition, I also want to ask 1 whether all threads can truly stop, and all threads will stop immediately after the stop point.

So let's go ahead and add pause and continue, and for the same reason, we use List < ManualResetEvent > .


List<BackgroundWorker> bws = new List<BackgroundWorker>();
 List<ManualResetEvent> mrs = new List<ManualResetEvent>();  
 int t = 10;

 private void button1_Click(object sender, EventArgs e)
 {
     for (int i = 0; i < t; i++)
     {
         BackgroundWorker bw = new BackgroundWorker();
         bw.DoWork += new DoWorkEventHandler(bw_DoWork);
         bw.WorkerSupportsCancellation = true;
         bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
         bws.Add(bw);

         bw.RunWorkerAsync(i);

         mrs.Add(new ManualResetEvent(true));
     }
 }

 private void button2_Click(object sender, EventArgs e)
 {
     for (int i = 0; i < t; i++)
     {
         bws[i].CancelAsync();
     }
 }

 private void button3_Click(object sender, EventArgs e)
 {
     Button b = (Button)sender;   
     if (b.Text == " suspended ")   
     {   
         for (int i = 0; i < mrs.Count; i++)   
         {   
             mrs[i].Reset();   
         }   
         b.Text = " Continue to ";   
     }   
     else  
     {   
         for (int i = 0; i < mrs.Count; i++)   
         {   
             mrs[i].Set();   
         }   
         b.Text = " suspended ";   
     }   
 }

 void bw_DoWork(object sender, DoWorkEventArgs e)
 {
     int j = Convert.ToInt32(e.Argument); 
     for (int i = j; i < 1000; i = i + t) 
     {
         if (((BackgroundWorker)sender).CancellationPending)  
         {
             e.Cancel = true;
             return;
         }

         string item = String.Format(" thread {0} Manipulating data {1}", j + 1, i);

         this.Invoke((MethodInvoker)delegate
         {
             this.richTextBox1.Text += item + Environment.NewLine;
         });

         Thread.Sleep(200);
         mrs[j].WaitOne();  
     }
 }

 void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
 {
     bws.Remove(sender as BackgroundWorker);
     if (bws.Count == 0)
     {
         if (e.Cancelled)
         {
             this.richTextBox1.Text += " The thread has stopped ";
         }
         else
         {
             this.richTextBox1.Text += " The thread is done ";
         }
     }
 }

At this point, all of the code are presented, the multiple thread operation will bring a lot of unexpected problems, such as multiple threads at the same time the data is written to a file, multi-threaded update datatable etc, make software somehow automatically quit,. net2. 0 is not in the absolute thread-safe data sets, many bosses say with lock, but I also is 1 and half known solution to lock, also please grant instruction comment, what is said above is wrong, also please everybody many directions.


Related articles: