Jump to content
Xtreme .Net Talk

Recommended Posts

Posted

I have a test application that sends data from a new thread back to the main thread. I would prefer to use the BackgroundWorker class (which is perfect for doing this), but I can not, as explained in my earlier thread No BackgroundWorker Allowed.

 

I designed the custom class ThreadParameter (below) with a delegate and a corresponding an event. The event, in turn, is intended to Invoke the method in the main thread; however, whenever the method in the main thread is called and I attempt to access one of the controls on my form, I get an InvaidOperationException telling me that I can not access the control from a thread other than where it is owned. I thought I was!

 

So, I wrote an even more basic application that was Console based because I wanted to cut out all of the fat before posting it here. Ugh! The Console Application runs without a hitch, and it is mostly from cut and paste from my Windows Application!

 

Below are the "meat and potatoes" of my two basic applications. Could someone with vast knowledge (i.e. Marble Eater or Plausibly) kindly help me see the error to my ways?

 

Both versions (Windows and Console) take an instance of this custom class that I created as the thread's Start parameter:

public class ThreadParameter {

 string _serialNumber;
 public delegate void ReportProgressDelegate(int step, DataTable data);
 public event ReportProgressDelegate ProgressChanged;

 public ThreadParameter(string serialNumber) {
   _serialNumber = serialNumber;
   ProgressChanged = null;
 }

 public void ReportProgress(int step, DataTable data) {
   if (ProgressChanged != null) {
     ProgressChanged.Invoke(step, data);
   }
 }

 public string SerialNumber { get { return _serialNumber; } }

}

 

Here is the Windows version, which throws the InvalidOperationException:

public partial class Form1 : Form {

 public Form1() {
   InitializeComponent();
 }

 void button1_Click(object sender, EventArgs e) {
   Go();
 }

 void Go() {
   listView1.Clear();
   listView1.Columns.Add("TestName", 140);
   listView1.Columns.Add("Result", (listView1.Width - listView1.Columns[0].Width - 2));
   string serialNumber = "123456789A";
   ThreadParameter tp = new ThreadParameter(serialNumber);
   tp.ProgressChanged += new ThreadParameter.ReportProgressDelegate(Thread_Report);
   Thread th = new Thread(new ParameterizedThreadStart(Thread_Routine));
   th.IsBackground = true;
   th.Start(tp);
   while (!th.IsAlive) Thread.Sleep(0);
   button1.Enabled = false;
   th.Join();
   button1.Enabled = true;
 }

 void Thread_Report(int step, object data) {
   if ((data != null) && (data is DataTable)) {
     DataTable table = (DataTable)data;
     const string TEST_RESULT = "Test_Result";
     switch (step) {
       case 0:
         listView1.Items.Add(new ListViewItem(new string[] { "Row 1", string.Empty }));
         listView1.Items.Add(new ListViewItem(new string[] { "Row 2", string.Empty }));
         listView1.Items.Add(new ListViewItem(new string[] { "Row 3", string.Empty }));
         listView1.Items.Add(new ListViewItem(new string[] { "Row 4", string.Empty }));
         listView1.Items.Add(new ListViewItem(new string[] { "Row 5", string.Empty }));
         listView1.Items.Add(new ListViewItem(new string[] { "Row 6", string.Empty }));
         break;
       case 1:
         if ((2 < table.Rows.Count) && (table.Columns.Contains(TEST_RESULT))) {
           listView1.Items[0].SubItems[1].Text = table.Rows[1][TEST_RESULT].ToString();
           listView1.Items[1].SubItems[1].Text = table.Rows[2][TEST_RESULT].ToString();
         }
         break;
       case 2:
         if ((4 < table.Rows.Count) && (table.Columns.Contains(TEST_RESULT))) {
           listView1.Items[2].SubItems[1].Text = table.Rows[3][TEST_RESULT].ToString();
           listView1.Items[3].SubItems[1].Text = table.Rows[4][TEST_RESULT].ToString();
         }
         break;
       case 3:
         if ((6 < table.Rows.Count) && (table.Columns.Contains(TEST_RESULT))) {
           listView1.Items[4].SubItems[1].Text = table.Rows[5][TEST_RESULT].ToString();
           listView1.Items[5].SubItems[1].Text = table.Rows[6][TEST_RESULT].ToString();
         }
         break;
       default:
         break;
     }
   }
 }

 void Thread_Routine(object threadParam) {
   ThreadParameter tp = (ThreadParameter)threadParam;
   const string sqlConn = "SUPPLY_YOUR_CONNECTION_STRING";
   using (SqlConnection conn = new SqlConnection(sqlConn)) {
     int step = 0;
     string sqlText = "sp_GetPartRecord";
     DataSet ds = new DataSet();
     using (DataTable table = new DataTable()) {
       tp.ReportProgress(step++, table);
     }
     using (SqlDataAdapter da = new SqlDataAdapter(sqlText, sqlConn)) {
       DataTable table = new DataTable();
       da.SelectCommand.CommandType = CommandType.StoredProcedure;
       da.SelectCommand.Parameters.AddWithValue("@SN", tp.SerialNumber);
       da.Fill(table);
       ds.Tables.Add(table);
       tp.ReportProgress(step++, ds.Tables[ds.Tables.Count - 1]);
     }
     using (SqlDataAdapter da = new SqlDataAdapter(sqlText, sqlConn)) {
       DataTable table = new DataTable();
       da.SelectCommand.CommandType = CommandType.StoredProcedure;
       da.SelectCommand.Parameters.AddWithValue("@SN", tp.SerialNumber);
       da.Fill(table);
       ds.Tables.Add(table);
       tp.ReportProgress(step++, ds.Tables[ds.Tables.Count - 1]);
     }
     using (SqlDataAdapter da = new SqlDataAdapter(sqlText, sqlConn)) {
       DataTable table = new DataTable();
       da.SelectCommand.CommandType = CommandType.StoredProcedure;
       da.SelectCommand.Parameters.AddWithValue("@SN", tp.SerialNumber);
       da.Fill(table);
       ds.Tables.Add(table);
       tp.ReportProgress(step++, ds.Tables[ds.Tables.Count - 1]);
     }
   }
 }

}

 

Getting this to work would be a considerable milestone for me.

 

If (after getting the code itself to work) someone sees bad logic or bad techniques, I would very much like to hear your input. Years ago, my major was physics, so I don't always take the best approach to software development.

 

Regards,

Joe

Posted

Here is the Console version, which does not throw an exception (though I can't see the output)

class Program {

 Button button1;
 ListView listView1;

 static void Main(string[] args) {
   Program pgm = new Program();
   pgm.Go();
 }

 public void Go() {
   button1 = new Button();
   button1.Enabled = true;
   listView1 = new ListView();
   listView1.Width = 500;
   listView1.Clear();
   listView1.Columns.Add("TestName", 140);
   listView1.Columns.Add("Result", (listView1.Width - listView1.Columns[0].Width - 2));
   string serialNumber = "123456789A";
   ThreadParameter tp = new ThreadParameter(serialNumber);
   tp.ProgressChanged += new ThreadParameter.ReportProgressDelegate(Thread_Report);
   Thread th = new Thread(new ParameterizedThreadStart(Thread_Routine));
   th.IsBackground = true;
   th.Start(tp);
   while (!th.IsAlive) Thread.Sleep(0);
   button1.Enabled = false;
   th.Join();
   button1.Enabled = true;
 }

 void Thread_Report(int step, object data) {
   if ((data != null) && (data is DataTable)) {
     DataTable table = (DataTable)data;
     const string TEST_RESULT = "Test_Result";
     switch (step) {
       case 0:
         listView1.Items.Add(new ListViewItem(new string[] { "Row 1", string.Empty }));
         listView1.Items.Add(new ListViewItem(new string[] { "Row 2", string.Empty }));
         listView1.Items.Add(new ListViewItem(new string[] { "Row 3", string.Empty }));
         listView1.Items.Add(new ListViewItem(new string[] { "Row 4", string.Empty }));
         listView1.Items.Add(new ListViewItem(new string[] { "Row 5", string.Empty }));
         listView1.Items.Add(new ListViewItem(new string[] { "Row 6", string.Empty }));
        break;
       case 1:
         if ((2 < table.Rows.Count) && (table.Columns.Contains(TEST_RESULT))) {
           listView1.Items[0].SubItems[1].Text = table.Rows[1][TEST_RESULT].ToString();
           listView1.Items[1].SubItems[1].Text = table.Rows[2][TEST_RESULT].ToString();
         }
         break;
       case 2:
         if ((4 < table.Rows.Count) && (table.Columns.Contains(TEST_RESULT))) {
           listView1.Items[2].SubItems[1].Text = table.Rows[3][TEST_RESULT].ToString();
           listView1.Items[3].SubItems[1].Text = table.Rows[4][TEST_RESULT].ToString();
         }
         break;
       case 3:
         if ((6 < table.Rows.Count) && (table.Columns.Contains(TEST_RESULT))) {
           listView1.Items[4].SubItems[1].Text = table.Rows[5][TEST_RESULT].ToString();
           listView1.Items[5].SubItems[1].Text = table.Rows[6][TEST_RESULT].ToString();
         }
         break;
       default:
         break;
     }
   }
 }

 void Thread_Routine(object threadParam) {
   ThreadParameter tp = (ThreadParameter)threadParam;
   const string sqlConn = "SUPPLY_YOUR_CONNECTION_STRING";
   using (SqlConnection conn = new SqlConnection(sqlConn)) {
     int step = 0;
     string sqlText = "sp_GetPartRecord";
     DataSet ds = new DataSet();
     using (DataTable table = new DataTable()) {
       tp.ReportProgress(step++, table);
     }
     using (SqlDataAdapter da = new SqlDataAdapter(sqlText, sqlConn)) {
       DataTable table = new DataTable();
       da.SelectCommand.CommandType = CommandType.StoredProcedure;
       da.SelectCommand.Parameters.AddWithValue("@SN", tp.SerialNumber);
       Console.WriteLine("Step {0}", step);
       da.Fill(table);
       ds.Tables.Add(table);
       tp.ReportProgress(step++, ds.Tables[ds.Tables.Count - 1]);
     }
     using (SqlDataAdapter da = new SqlDataAdapter(sqlText, sqlConn)) {
       DataTable table = new DataTable();
       da.SelectCommand.CommandType = CommandType.StoredProcedure;
       da.SelectCommand.Parameters.AddWithValue("@SN", tp.SerialNumber);
       Console.WriteLine("Step {0}", step);
       da.Fill(table);
       ds.Tables.Add(table);
       tp.ReportProgress(step++, ds.Tables[ds.Tables.Count - 1]);
     }
     using (SqlDataAdapter da = new SqlDataAdapter(sqlText, sqlConn)) {
       DataTable table = new DataTable();
       da.SelectCommand.CommandType = CommandType.StoredProcedure;
       da.SelectCommand.Parameters.AddWithValue("@SN", tp.SerialNumber);
       Console.WriteLine("Step {0}", step);
       da.Fill(table);
       ds.Tables.Add(table);
       tp.ReportProgress(step++, ds.Tables[ds.Tables.Count - 1]);
     }
   }
 }
}

  • Leaders
Posted

Looking at just your console app, I changed the definition of Program to

   class Program
   {

       Button button1;
       ListView listView1;
       [color="Red"]public Thread MainThread;[/color]

       static void Main(string[] args) {
           Program pgm = new Program();
           [color="Red"]pgm.MainThread = Thread.CurrentThread;[/color]

           pgm.Go();
           Console.ReadLine();
       }
/*
       .
       .
       .    
*/

       void Thread_Report(int step) {
           [color="Red"]Console.WriteLine((Thread.CurrentThread == MainThread).ToString());[/color]
       }
// ...

The output was false. It looks as if you expect the Delegate.Invoke method to behave as the Control.Invoke method does. Delegate.Invoke will run the target method on the thread from which it is called.

 

For a quick solution, you could change the Thread_Report method as follows:


 void Thread_Report(int step, object data) {
[color="Red"]    if (InvokeRequired) {
       Invoke(new AppropriateDelegateType(Thread_Report), new object[] { step, data });
       return;
   }[/color]

   if ((data != null) && (data is DataTable)) {
     DataTable table = (DataTable)data;
//  ...

You would have to do this for each cross-thread invocation however.

 

The background-worker uses a more elaborate technique for the thread-safe calls, based on a SynchronizationContext object.

[sIGPIC]e[/sIGPIC]
Posted

THANK YOU!

 

I do not use delegates on a daily basis, but I have found myself using them more as I become more familiar with them.

 

A distinction between Control.Invoke and Delegate.Invoke was not something I would have found on my own, and I could not think of a way to test to see if I was actually on a different thread or not. I briefly thought about trying to create handle variables for each of the threads that I could look at in the debugger, but I just did not see any way that Invoke could *not* be working. (Control.Invoke != Delegate.Invoke) was what I was missing.

 

With that, I can look into how to patch my little class.

 

The BackgroundWorker is fabulous, and that's what I started using; however, the files that the code resides in is linked in to the main project along with a Windows Mobile project - and the Windows Mobile (or Compact Framework) does not include a BackgroundWorker. This is a stab at creating something similar.

 

Again, Thank You! What bar can I leave $20 for your beer tab?

Posted

Rats. I thought I did this right, but my application now hangs when I call Invoke.

 

I'm hoping someone can show me my error from my code (a small 12.9 KB attachment).

 

The solution was created with VS2005, and it will appear to have 2 forms in it when viewed through the Solution Explorer. The forms will not display in the Designer, because they are created using a class that is derived from System.Windows.Form.

 

If someone can take the time to look this over, the small application will hang on Line 25 of the file ZParameters.cs "_owner.Invoke(methInvoker);".

 

Yes, I have created this so that it looks a lot like the BackgroundWorker class, but Windows Mobile does not support this class. I'm trying to create a threading class that sends data back to the main thread as it collects information.

 

Help and Pointers are appreciated.

 

~Joe

PostingThread.zip

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...