调用与表单加载并行更新标签的函数,并允许表单继续加载
本文关键字:加载 表单 许表单 继续 函数 并行 更新 标签 调用 | 更新日期: 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页面找到。