代码之家  ›  专栏  ›  技术社区  ›  iandisme

从多个工作线程(.net)更新用户界面

  •  6
  • iandisme  · 技术社区  · 15 年前

    我正在做一个宠物项目,它有多个工作线程。将所有内容输出到控制台越来越难理解,所以我想开发一个UI,每个线程有一个输出区域。我想知道线程向UI发送更新的最佳方式。我有两个想法:

    1)当新数据可用时,让每个线程设置一个“dataupdated”标志,并让UI定期检查新数据。

    2)创建每个线程,其中回调一个UI更新(…)方法,以便在新数据可用时调用该方法。

    我目前倾向于(2)有两个原因:我不喜欢“检查”每个线程的想法,因为这是我的第一个多线程应用程序,(2)看起来比它可能简单。我想知道:

    • 在简单和高效方面,哪种选择更可取?
    • 您对实现(2)或类似的东西(即更多的事件驱动)有什么建议吗?
    6 回复  |  直到 15 年前
        1
  •  12
  •   itowlson    15 年前

    通过创建BackgroundWorker组件并在其DoWork处理程序中执行工作,可以轻松实现(2):

    BackgroundWorker bw = new BackgroundWorker();
    bw.WorkerReportsProgress = true;
    bw.DoWork += /* your background work here */;
    bw.ProgressChanged += /* your UI update method here */;
    bw.RunWorkerAsync();
    

    每个后台工作人员都可以通过调用report progress向UI线程报告进度:尽管这主要是为报告有界进程的进度而设计的,但这不是必需的——如果您的UI更新需要这样做,您也可以传递自己的自定义数据。您将从DoWork处理程序调用ReportProgress。

    BackgroundWorker的好处在于它为您处理了许多混乱的交叉线程细节。它还符合事件驱动的更新模型,您(正确地)更喜欢显式回调。

        2
  •  1
  •   Austin Salonen gmlacrosse    15 年前

    我也投了2的票,但是 BackgroundWorkers 而不是System.Threading.Threads。

        3
  •  1
  •   Dr. Wily's Apprentice    15 年前

    在大多数情况下,最容易做的事情就是使用Itowlson答案中建议的BackgroundWorker组件,如果可能的话,我强烈建议使用这种方法。如果出于某种原因,您不能为自己的目的使用BackgroundWorker组件,例如使用.NET 1.1(Yikes!)或者使用紧凑的框架,那么您可能需要使用另一种方法:

    对于WinForm控件,您必须避免修改除最初创建控件的线程之外的任何线程上的控件。BackgroundWorker组件为您处理这个问题,但是如果您不使用它,那么您可以并且应该使用System.Windows.Forms.Control类上的invokeRequired属性和invoke方法。下面是使用此属性和方法的示例:

    public partial class MultithreadingForm : Form
    {
        public MultithreadingForm()
        {
            InitializeComponent();
        }
    
        // a simple button event handler that starts a worker thread
        private void btnDoWork_Click(object sender, EventArgs e)
        {
            Thread t = new Thread(WorkerMethod);
            t.Start();
        }
    
        private void ReportProgress(string message)
        {
            // check whether or not the current thread is the main UI thread
            // if not, InvokeRequired will be true
            if (this.InvokeRequired)
            {
                // create a delegate pointing back to this same function
                // the Invoke method will cause the delegate to be invoked on the main UI thread
                this.Invoke(new Action<string>(ReportProgress), message);
            }
            else
            {
                // txtOutput is a UI control, therefore it must be updated by the main UI thread
                if (string.IsNullOrEmpty(this.txtOutput.Text))
                    this.txtOutput.Text = message;
                else
                    this.txtOutput.Text += "\r\n" + message;
            }
        }
    
        // a generic method that does work and reports progress
        private void WorkerMethod()
        {
            // step 1
            // ...
            ReportProgress("Step 1 completed");
    
            // step 2
            // ...
            ReportProgress("Step 2 completed");
    
            // step 3
            // ...
            ReportProgress("Step 3 completed");
        }
    }
    
        4
  •  0
  •   TLiebe    15 年前

    您可以让工作线程引发事件,并让主UI线程添加事件处理程序。您需要小心,不要引发太多的事件,因为如果您的工作线程每秒引发多个事件,则可能会变得很难看。

    This 文章给出了一个快速的概述。

        5
  •  0
  •   Mez    15 年前

    在应用程序中实现多线程的首选方法是使用 BackgroundWorker 组件。BackgroundWorker组件使用事件驱动模型进行多线程处理。工作线程运行DoWork事件处理程序,创建控件的线程运行ProgressChanged和RunWorkerCompleted事件处理程序。
    当您在ProgressChanged事件处理程序中更新UI控件时,它们将在主线程上自动更新,这将阻止您获取跨线程异常。

    here 例如如何使用BackgroundWorker。

        6
  •  0
  •   Ed Power    15 年前

    如果要创建自己的线程(非BackgroundWorker或ThreadPool线程),可以从主线程传递一个回调方法,该方法是从工作线程调用的。这还允许您将参数传递给回调,甚至返回一个值(例如go/no go标志)。在回调中,通过目标控件的调度程序更新UI:

    public void UpdateUI(object arg)
    {
        controlToUpdate.Dispatcher.BeginInvoke(
            System.Windows.Threading.DispatcherPriority.Normal
            , new System.Windows.Threading.DispatcherOperationCallback(delegate
            {
                controToUpdate.property = arg;
                return null;
            }), null);
        }
    }