跨线程操作-新手问题 [英] Cross thread operations - Newbie question

查看:93
本文介绍了跨线程操作-新手问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

大家好,

背景信息:我有一个应用程序,在其中我不断通过串行端口向微控制器发送命令,并处理来自串行端口的响应.在此过程中,我在主窗体上获取并设置了各种控件属性.
当我启动此主进程时,我无法单击主表单上的任何按钮,也无法选中/取消选中任何复选框等.我猜这是因为所有内容都在单个线程上运行...?
因此,现在我创建了一个单独的线程来运行此过程,但是我仍然需要从主窗体上的控件中更新并获取信息.
我的问题是:我是否需要为我在主窗体上的控件上执行的每个动作创建一个委托"功能,或者是否有其他方法可以做到这一点?我在主窗体上做了很多工作,因此将涉及创建很多委托函数.

任何帮助将不胜感激.

谢谢
新手安德烈:)

这是一些代码来说明:

HI All,

Background info: I have an application where I continually send commands to a microcontroller via a serial port and processing the responses from the Serial port. I get and set various control properties on the main form during this process.
When I start this main process I cannot click any buttons on the main form or check/uncheck any checkboxes etc etc. I guess this is because everything runs on a single thread...?
So now I created a seperate thread to run this process in but I still need to update and get info from controls on the main Form.
My question is: Do I need to create a "Delegate" function for each action I perform on a control on the main Form or is there some other way of doing this. There is quite a lot of stuff I do on the main Form so it will involve creating a LOT of delegate functions.

Any help will be appreciated.

Thanks
Newbie Andre :)

Here is some code to illustrate:

protected void start_tests()   //this method runs in its own thread
        {
            string input = string.Empty;
            timer1.Enabled = false;     // this would need delegate?
            if (ResetFlag == true)      //I guess this would need delegate
            {
                reset_selectedindex(); //delegate allready created
                checkedListBox1.SelectedIndex = 0; //this would need delegate
                ResetFlag = false;   //I think this would need delegate
            }
            progressBar1.Maximum = checkedListBox1.CheckedItems.Count;  //this needs delegate
            progressBar1.Value = 0;  //this needs delegate

            while (checkedListBox1_selectedindex() < checkedListBox1_Items_Count())  //delegates used here
            {
                if (checkedListBox1.GetItemCheckState(checkedListBox1.SelectedIndex) == CheckState.Checked)  //delegates needed here
                {
                    UpdateText(checkedListBox1.SelectedItem.ToString() + "\n", UpdateType.TXdata,Color.Lime);  //delegates needed here
                    progressBar1.Value++;   //delegate needed here ....etc etc etc
                    try
                    {
                        show_message_box(checkedListBox1.SelectedIndex,PrePostCommand.precommand);
                        input = ExecCommand("TESTSEQ" + Convert.ToDecimal(checkedListBox1.SelectedIndex).ToString().PadLeft(2, '0') + "\r", 3000, "No responese from OVPT");
                        if (input.Contains("REQUEST"))
                        {
                            if (DialogResult.Yes == show_message_box(checkedListBox1.SelectedIndex,PrePostCommand.postcommand))
                            {
                                UpdateText("TEST:" + Convert.ToDecimal(checkedListBox1.SelectedIndex).ToString().PadLeft(2, '0') + " - " + checkedListBox1.SelectedItem.ToString() + "\r\n", UpdateType.MessageData, Color.Lime);
                                UpdateText("TEST PASS.", UpdateType.MessageData, Color.Lime);
                            }
                            else
                            {
                                UpdateText("TEST:" + Convert.ToDecimal(checkedListBox1.SelectedIndex).ToString().PadLeft(2, '0') + " - " + checkedListBox1.SelectedItem.ToString() + "\r\n", UpdateType.MessageData, Color.Red);
                                UpdateText("TEST FAIL.", UpdateType.MessageData, Color.Lime);
                                TestPass = false;
                            }

                        }
                        if (input.Contains("FAIL"))
                        {
                            UpdateText("TEST:" + Convert.ToDecimal(checkedListBox1.SelectedIndex).ToString().PadLeft(2, '0') + " - " + checkedListBox1.SelectedItem.ToString() + "\r\n", UpdateType.MessageData, Color.Red);
                            UpdateText(input, UpdateType.MessageData, Color.Red);
                            TestPass = false;
                        }
                        if (input.Contains("PASS"))
                        {
                            UpdateText("TEST:" + Convert.ToDecimal(checkedListBox1.SelectedIndex).ToString().PadLeft(2, '0') + " - " + checkedListBox1.SelectedItem.ToString() + "\r\n", UpdateType.MessageData, Color.Lime);
                            UpdateText(input, UpdateType.MessageData, Color.Lime);
                        }
                    }
                    catch (Exception e)
                    {
                        UpdateText(e.Message, UpdateType.MessageData,Color.Red);
                        TestPass = false;
                    }

                }
                if (checkedListBox1.SelectedIndex == (checkedListBox1.Items.Count - 1))
                {
                    break;
                }
                checkedListBox1.SelectedIndex++;

            }
            if (TestPass == false)
            {
                label_Result.Text = "FAILED!!";
                label_Result.BackColor = Color.Red;
                label_Result.ImageIndex = 0;
            }
            else
            {
                label_Result.Text = "PASS!!";
                label_Result.BackColor = Color.Lime;
                label_Result.ImageIndex = 1;
            }

            if (checkBox3.Checked == true)
            {
                Thread.Sleep(100);
                button2_Click_1(this, EventArgs.Empty);
                Thread.Sleep(20);
                //start_tests();
            }
            else
            {
                timer1.Enabled = true;
                oThread.Abort();
            }

        }

推荐答案

事件处理程序和Control.Invoke方法(委托)是一种不错的方法,但是如果您使用的是.net 4.0,请不要使用必须先使用事件,然后再使用SynchronizationContext,这非常容易使用,请在主窗体上使用以下代码来获取SynchronizationContext
Event-handlers and Control.Invoke Method (Delegate) is a good approach but if you are using .net 4.0 and don''t have necessity to use events then use SynchronizationContext it is very easy and simple to use, use following code on your main form to get SynchronizationContext
context = SynchronizationContext.Current;


并使用此回调从线程中更新主表单的组件


and use this callback to update components of your main form from your thread

SendOrPostCallback callback = delegate(object obj)
{
//Update all the objects here which need delegate
checkedListBox1.SelectedIndex = 0; //no more need delegate
ResetFlag = false;   //no more need delegate
};
context.Post(callback, null);


UI和业务逻辑的混合令人恐惧这里.很高兴您跨过了这个跨线程问题,发现您拥有所有需要调用的地方,因为它显示了真正的问题–您正在尝试运行测试并执行UI都在同一个地方工作,这会导致凌乱的代码.

因此,现在我创建了一个单独的线程来运行此过程,但是我仍然需要从主窗体上的控件中更新并获取信息."
不,你不会.您仍然需要从也在主窗体上显示的数据模型中获取信息,并且需要能够发出进度和完成信号.运行测试的线程对用户界面一无所知.

概括地说,您想要的是类似
的东西
There is some horrible mixing of UI and business logic going on here. It''s a good thing you tripped over this cross threading issue and are discovering that you have all those places where you''d need to Invoke, because it shows up the real problem – you''re trying to run the tests and do UI work all in the same place, and that leads to messy code.

"So now I created a seperate thread to run this process in but I still need to update and get info from controls on the main Form."
No you don''t. You still need to get information from the data model which is also being displayed on the main form, and you need to be able to signal progress and completion. The thread running the tests shouldn''t know anything about the UI.

Broadly speaking what you want is something like
class TestRunner {
 public event EventHandler<IntEventArgs> Completed;
 public event EventHandler<IntEventArgs> Progress;
 public event EventHandler<TestCaseEventArgs> CaseComplete;
 
 public List<decimal> TestCases { get; set; }

 public void RunTests(){
  int fails = 0;

  for(int i = 0; i < TestCases.Count; i++){
   decimal d = TestCases[i];
   input = ExecCommand("TESTSEQ" + d.ToString().PadLeft(2, ''0'') + "\r", 3000, "No responese from OVPT");
   // ... etc

   if(!TestPassed) fails++;
   
   EventHandler<TestCaseEventArgs> completeHandler = CaseComplete; 
   if(null != completeHandler) completeHandler(this, new TestCaseEventArgs(i, d, TestPassed);

   EventHandler<IntEventArgs> progressHandler = Progress; 
   if(null != progressHandler) progressHandler(this, new IntCaseEventArgs(i);

  }

  EventHandler<IntEventArgs> completedHandler = Completed; 
  if(null != completedHandler) completedHandler(this, new IntCaseEventArgs(fails);
 }

 public class TestCaseEventArgs: EventArgs {
  public int Index { get; private set; }
  public decimal Value { get; private set; }
  public bool Passed { get; private set; }
  public TestCompleteEventArgs(int index, decimal value, bool passed) { Index = index; Value = value; Passed = passed; }
 } 
}


public class IntEventArgs: EventArgs {
 public int Value { get; private set; }
 public TestCompleteEventArgs(int value) { Value = value; }
}



然后,您的主窗体应构造用于测试运行的输入数据并挂接事件.事件处理程序将需要使用Invoke或BeginInvoke,但这仅是3种方法.



Then your main form should construct the input data for the test run and hook the events. The event handlers will need to use Invoke or BeginInvoke, but that''s only 3 methods.

class MainForm {
 // ...

 TestRunner testRunner = new TestRunner();

 private void StartTests(){
  Thread t = new Thread(testRunner.RunTests);
  SetControlState(false); // You''ll want to disable most of the UI when a test is running

  // Set up the test cases
  List<decimal> cases = new List<decimal>();
  for(int i = 0; i < checkedListBox1.Items.Count; i++){
   if(checkedListBox1.Items[i].Selected) cases.Add(i);
  }
  progressBar.MaxValue = cases.Count;
  testRunner.TestCases = cases;

  t.Start();
 }

 public MainForm(){
  // some stuff already here

  testRunner.Completed += TestComplete;
  testRunner.Progress += TestProgress;
  testRunner.CaseComplete += CaseComplete;
 }
 
 private void TestProgress(object sender, IntEventArgs e){
  Invoke( () => progressBar.Value = e.Value );
 }

 private void CaseComplete(object sender, TestRunner.TestCaseEventArgs e){
  Invoke ( () => {
   // stuff to do in the UI for a case
  });
 } 

 private void TestComplete(object sender, IntEventArgs e){
  Invoke( ()=> {
            if (e.Value > 0)
            {
                label_Result.Text = "FAILED!!";
                label_Result.BackColor = Color.Red;
                label_Result.ImageIndex = 0;
            }
            else
            {
                label_Result.Text = "PASS!!";
                label_Result.BackColor = Color.Lime;
                label_Result.ImageIndex = 1;
            }

    SetControlState(true);
  } );

  void SetControlState(bool state){
   // Set various controls to enabled or disabled in here
  }
 }



(如果您不使用.Net 4,则必须使用适当的方法或匿名委托进行调用,而不是前一阵子显示的那整齐的lambda技巧.)

可能有一个用于测试运行程序的BackgroundWorkerThread,它报告进度和完成情况,但是我不确定它会带来很多好处(这里的线程类非常简单).



(If you''re not using .Net 4 you''ll have to Invoke on a proper method or anonymous delegate, instead of this rather neat lambda trick I was shown a while back.)

It''s probably possible to have a BackgroundWorkerThread for the test runner, which reports progress and completion, but I''m not sure it gains a lot (the thread class is pretty simple here).


这篇关于跨线程操作-新手问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆