调用与表单加载并行更新标签的函数,并允许表单继续加载

本文关键字:加载 表单 许表单 继续 函数 并行 更新 标签 调用 | 更新日期: 2023-09-27 18:06:58

我在表单加载期间调用了几个函数,然后使用结果来更新标签控件。如果函数长时间运行,它会阻止表单加载…所以我想让它的形式load a工作,而两个函数并行调用,当它们返回,然后更新标签,这是调用代码,两个函数返回一个int。

这是一个windows窗体应用程序在。net 4.0

private void mainForm_Load(object sender, EventArgs e)
   {
       currentCount  = func1(tes1);
       allowedCount= func2(test2);
       labelCount.Text = "Using " + func1.ToString() + " of " + func2.ToString();
   }

调用与表单加载并行更新标签的函数,并允许表单继续加载

更新日期:2001-08-27添加连续更新

我更新了代码,还展示了如何使用. forms GUI计时器组件在后台更新发生时更新状态栏标签,还使用了一些各种清理和附加注释。当然也可以使用Invoke()在表单输入信息时更新表单,但我认为这样做没有多大意义。UI更新的目标是为用户提供足够的视觉反馈,并且不需要将字段的更新与视觉元素的更新耦合起来。

同样,如果这些更新出现得足够频繁,你可能会因为所有调用Invoke而受到很大的性能影响,除非你对它们进行速率限制,这基本上就是计时器组件所发生的事情。

我已经压缩了解决方案,并提供下载:ParallelButtons-7208779.zip。我不完全确定为什么我选择了这个名字,但就是这样。:)

因此,代码现在演示了如何做到这两件事:我最初解释的问题是什么意思(在后台并行运行两个线程,一旦两个函数都返回就更新标签),以及在后台线程中添加单独的计数器字段的定期更新,这些线程也在GUI上显示状态,由Timer组件在其Tick事件中更新标签实现。

对于基于线程结束时的最终更新,这种设置不需要任何超出结构已经提供的同步,至少从更新事物的角度来看;我不知道实际的函数是什么。(如果它们彼此交互,它们可能有一些同步需求。)它还可以非常快速地更新表单,甚至不必担心两个线程之间的任何协调或同步,以便找出哪个负责更新GUI。

<<h3>形式代码/h3>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
namespace ParallelButtons_7208779
{
    public partial class frmMain : Form
    {
        public frmMain()
        {
            InitializeComponent();
            tslblRunStatus.Text = "Updating: please wait...";
            tslblFinalStatus.Text = "";
            Thread BackgroundThread =
                new Thread(() => TwoParallelCalls_UpdateOnlyOnReturn());
            BackgroundThread.Start();
            // tmrUpdateStatus is a timer component dropped onto the
            // form in design mode. it's initial settings are defaults
            // Interval=100, Enabled=false, and it's Tick event has 
            // been hooked up to tmrUpdateStatus_tick
            tmrUpdateStatus.Start();
        }
        // the nice thing about the component timer is that we don't 
        // have to worry about doing an Invoke, we already know that the
        // Tick event is happening on the UI thread.
        private void tmrUpdateStatus_Tick(object sender, EventArgs e)
        {
            // in case a tick fires after both functions complete
            if ((currentCount == -1) || (allowedCount != 1))
            {
                tslblRunStatus.Text =
                    string.Format(
                        "[running...] Using {0} of {1}",
                        runCurrentCount, runAllowedCount
                    );
            }
            else
            {
                // We can use this to stop the timer since we are doing the
                // check in here. If we didn't need to prevent an extra
                // update after the functions were complete, we could skip
                // the check in here and stop it elsewhere. (see below)
                tmrUpdateStatus.Stop();
            }
        }
        // The nice thing about having a common method that fires both 
        // functions and then waits for both is that no special thread
        // synchronization is needed.
        //
        // Otherwise there would be a need to use some sort of 
        // sychronization method (e.g. Semaphore, Mutex, lock) to ensure 
        // that the update is handled correctly.
        private void TwoParallelCalls_UpdateOnlyOnReturn()
        {
            // initializing with Lambdas that just set the fields to the
            // result of the function calls.
            Thread thread1 = new Thread(() => currentCount = func1());
            Thread thread2 = new Thread(() => allowedCount = func2());
            // start both threads and wait for both to finish
            thread1.Start(); 
            thread2.Start();            
            thread1.Join(); 
            thread2.Join();
            // using Invoke to safely update the .Forms GUI component.
            Invoke((Action)
                (() => 
                {
                    // this stops the UI update timer from this function, 
                    // we can do this instead of checking status in the
                    // Tick event if we can tolerate extra updates.
                    tmrUpdateStatus.Stop();
                    // set the final status test to the label
                    tslblFinalStatus.Text =
                        string.Format(
                            "[final] Used {0} of {1}",
                            currentCount, allowedCount
                        );
                }
                ));
        }
        #region Background Thread Functionality
        // The following functions are just dummy methods to udpate something
        // in the background we can use to watch the results on the UI.
        // In this section, I just have two methods that run in the background
        // updating some member fields. 
        // fields for intermediate values (set internally while running)
        int runCurrentCount = -1;
        int runAllowedCount = -1;
        // fields for a final result (set externally using return value)
        int currentCount = -1;
        int allowedCount = -1;
        // holds how long we want the test threads to run
        TimeSpan TestRunTimespan = TimeSpan.FromSeconds(5);
        // These methods use the System.Diagnostics.Stopwatch class which
        // has been around since .NET 2.0.
        // If you are really wanting to do a task that requires something
        // to happen at particular intervals, you should probably look at
        // an interval timer of some sort. There are several timers
        // available, There are various concerns when choosing one, so 
        // I highly recommend doing research (Stack Overflow has some
        // good answers on this issue if you search on 'Timer'.)
        //
        // Timers: System.Windows.Forms.Timer, System.Threading.Timer,
        // System.Windows.Threading.DispatcherTimer, System.Timers.Timer
        private int func1()
        {
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            while (stopWatch.Elapsed < TestRunTimespan)
            {
                runCurrentCount += 5;
                Thread.Sleep(100);
            }
            runCurrentCount += 10;
            return runCurrentCount;
        }
        private int func2()
        {
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            while (stopWatch.Elapsed < TestRunTimespan)
            {
                runAllowedCount += 10;
                Thread.Sleep(100);
            }
            runAllowedCount += 10;
            return runAllowedCount;
        }
        #endregion
    }
}

表单设计器代码

namespace ParallelButtons_7208779
{
    partial class frmMain
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.statusStrip1 = new System.Windows.Forms.StatusStrip();
            this.tslblRunStatus = new System.Windows.Forms.ToolStripStatusLabel();
            this.tslblFinalStatus = new System.Windows.Forms.ToolStripStatusLabel();
            this.tmrUpdateStatus = new System.Windows.Forms.Timer(this.components);
            this.statusStrip1.SuspendLayout();
            this.SuspendLayout();
            // 
            // statusStrip1
            // 
            this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
                this.tslblRunStatus,
                this.tslblFinalStatus});
            this.statusStrip1.Location = new System.Drawing.Point(0, 208);
            this.statusStrip1.Name = "statusStrip1";
            this.statusStrip1.Size = new System.Drawing.Size(431, 22);
            this.statusStrip1.TabIndex = 0;
            this.statusStrip1.Text = "statusStrip1";
            // 
            // tslblRunStatus
            // 
            this.tslblRunStatus.BorderStyle = System.Windows.Forms.Border3DStyle.SunkenInner;
            this.tslblRunStatus.Name = "tslblRunStatus";
            this.tslblRunStatus.Size = new System.Drawing.Size(80, 17);
            this.tslblRunStatus.Text = "Item {0} of {1}";
            // 
            // tslblFinalStatus
            // 
            this.tslblFinalStatus.Name = "tslblFinalStatus";
            this.tslblFinalStatus.Size = new System.Drawing.Size(60, 17);
            this.tslblFinalStatus.Text = "final status";
            // 
            // tmrUpdateStatus
            // 
            this.tmrUpdateStatus.Tick += new System.EventHandler(this.tmrUpdateStatus_Tick);
            // 
            // frmMain
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(431, 230);
            this.Controls.Add(this.statusStrip1);
            this.Name = "frmMain";
            this.Text = "Background Form Updates - Stack Overflow 7208779";
            this.statusStrip1.ResumeLayout(false);
            this.statusStrip1.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();
        }
        #endregion
        private System.Windows.Forms.StatusStrip statusStrip1;
        private System.Windows.Forms.ToolStripStatusLabel tslblRunStatus;
        private System.Windows.Forms.ToolStripStatusLabel tslblFinalStatus;
        private System.Windows.Forms.Timer tmrUpdateStatus;
    }
}

EDIT

一种更紧密的变体,直到func1和func2都完成后才更新标签:

private void UpdateCountInBackground()
{
    Task.Factory.StartNew(
        () =>
        {
            int current = 0, allowed = 0;
            Task.WaitAll(
                Task.Factory.StartNew(() => current = func1()),
                Task.Factory.StartNew(() => allowed = func2()));                   
            Invoke(new Action(() => 
                labelCount.Text = "Using " + current.ToString() + " of " + allowed.ToString()));
        });
}
原来

你可以在任务中做类似的事情。

private void mainForm_Load(object sender, EventArgs e)         
{        
    Task t1 = Task.Factory.StartNew<int>(func1).ContinueWith(t => { currentCount = t.Result; UpdateLabel(); });
    Task t2 = Task.Factory.StartNew<int>(func2).ContinueWith(t => { allowedCount = t.Result; UpdateLabel(); });
}

那么你需要:

private int currentCount, allowedCount;
private void UpdateLabel()
{
    if (InvokeRequired)
    {
        Invoke(new MethodInvoker(UpdateLabel));
    }
    else
        labelCount.Text = "Using " + currentCount.ToString() + " of " + allowedCount.ToString();
}

这将并行执行两个长时间运行的方法,当每个方法完成后,它将线程安全地更新标签。

你所需要做的就是创建一个新的线程并启动它。

private void mainForm_Load(object sender, EventArgs e)
{    
    var thread = new Thread(() =>
    {
        currentCount  = func1(tes1);
        allowedCount= func2(test2);
        labelCount.Text = "Using " + func1.ToString() + " of " + func2.ToString();
    });
    thread.Start();
}

在我看来,这就是你在winforms中所需要做的。>

试试这样:

new Thread(() => { /* make stuff and finally*/ formObject.Invoke });

在windows窗体中从非UI线程更新UI的更详细的例子可以在微软的Control页面找到。