joe_pool_is Posted June 25, 2009 Posted June 25, 2009 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 Quote Avoid Sears Home Improvement
joe_pool_is Posted June 25, 2009 Author Posted June 25, 2009 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]); } } } } Quote Avoid Sears Home Improvement
Leaders snarfblam Posted June 25, 2009 Leaders Posted June 25, 2009 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. Quote [sIGPIC]e[/sIGPIC]
joe_pool_is Posted June 26, 2009 Author Posted June 26, 2009 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? Quote Avoid Sears Home Improvement
joe_pool_is Posted June 29, 2009 Author Posted June 29, 2009 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. ~JoePostingThread.zip Quote Avoid Sears Home Improvement
Administrators PlausiblyDamp Posted June 29, 2009 Administrators Posted June 29, 2009 It is probably waiting on the th.Join(); statement in Form1, not had chance to have a proper look at the code but that is where I would start. Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
joe_pool_is Posted June 29, 2009 Author Posted June 29, 2009 Wow! You're right. I commented that out, and the hang went away. Well... how will I know when my thread has finished now? I was thinking my routine would join the thread and continue after the thread completed. I don't want to continually poll the IsAlive property. Quote Avoid Sears Home Improvement
Administrators PlausiblyDamp Posted June 29, 2009 Administrators Posted June 29, 2009 The thread could raise an event or accept a callback routine it calls when it's work is done. Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
joe_pool_is Posted June 29, 2009 Author Posted June 29, 2009 Eureka! This tool works, and I am posting it here for anyone who might have need of something like this. Thanks PlausiblyDamp and Marble_Eater!PostingThread.zip Quote Avoid Sears Home Improvement
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.