什么应该是直截了当不在这里,尽管阅读了很多,但我还是找不到办法。
我有一个执行耗时功能的按钮。因此,单击该按钮应显示间隔为500毫秒的标签中经过的时间(以毫秒为单位)。当达到预期的结果时,我希望计时器停止。 我不需要标签中的最终时间(消耗的总时间),但标签应该动态显示已过去的时间。我的代码将是:
private void btnHistory_Click(object sender, EventArgs e)
{
Class1 c = new Class1();
c.StartClock(ref label12);
Utility.PopulateHistory(dgvRecords_history, _util); //time consuming function
c.StopClock();
}
在Class1
我写这个:
internal void StartClock(ref Label l)
{
Timer t = new Timer();
t.Interval = 500;
t.Enabled = true;
t.Tag = l;
t.Tick += new EventHandler(t_Tick);
t.Start();
}
int i;
bool stop;
void t_Tick(object sender, EventArgs e)
{
if (stop)
{
((Timer)sender).Stop();
return;
}
((Label)((Timer)sender).Tag).Text = (++i).ToString();
}
internal void StopClock()
{
i = 0;
stop = true;
}
发生的情况是,t_Tick
事件仅在触发按钮事件下的完整代码后才会触发。这是tick事件在通过StopClock函数后触发!我不知道为什么它应该是那样的!
基本上有两个问题:
如何以正确的方式处理这些要求?我知道我应该使用其他内置类来评估性能,但这仅用于显示目的。为此,理想的方法是什么?
为什么我的代码无效?
编辑:我在这里使用了System.Windows.Forms Timer
,但结果与System.Timers Timer
没有任何区别
答案 0 :(得分:6)
问题是您的长时间运行任务也在UI线程上运行。因此,计时器无法触发并更新UI,因为线程正忙于处理长时间运行的任务。
相反,您应该使用BackgroundWorker来处理长时间运行的任务。
在代码中:
private void btnHistory_Click(object sender, EventArgs e)
{
Class1 c = new Class1(ref label12);
c.StartClock();
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (s, e) =>
{
// time consuming function
Utility.PopulateHistory(dgvRecords_history, _util);
};
backgroundWorker.RunWorkerCompleted += (s, e) =>
{
c.StopClock();
};
backgroundWorker.RunWorkerAsync();
}
正如ChrisWue所说,既然你现在在一个单独的线程中有长时间运行的任务,它需要调用对UI线程上的UI控件的任何访问。
如果长时间运行的任务只需要UI中的某些数据启动,则可以将该数据作为RunWorkerAsync()
的参数传递。如果需要将一些结果数据输出到UI,可以在RunWorkerCompleted
事件的处理程序中执行此操作。如果您偶尔需要在进行更新时更新UI,可以在ProgressChanged
事件的处理程序中执行此操作,在DoWork处理程序中调用ReportProgress()
。
如果不需要上述任何一项,您可以使用ThreadPool,就像StaWho的回答一样。
答案 1 :(得分:2)
耗时的功能阻塞了主线程。您可以使用BackgroundWorker
或以下技巧:
public Form1()
{
InitializeComponent();
t.Tick +=new EventHandler(t_Tick);
t.Interval = 500;
}
int timeElapsed = 0;
System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
private void button1_Click(object sender, EventArgs e)
{
t.Start();
ThreadPool.QueueUserWorkItem((x) =>
{
TimeConsumingFunction();
});
}
void TimeConsumingFunction()
{
Thread.Sleep(10000);
t.Stop();
}
void t_Tick(object sender, EventArgs e)
{
timeElapsed += t.Interval;
label1.Text = timeElapsed.ToString();
}
答案 2 :(得分:0)
将计时器添加到表单的Components集合中。或者将计时器存储在班级的字段中。
计时器是垃圾收集的,因为当方法返回时,它不再可用。
我不知道你的长时间运行的代码,但是如果新的运行在一个单独的线程上,或者调用Application.DoEvents
(并删除代码中的引用,不使用它。)
答案 3 :(得分:0)
@Dainel Rose的答案对我很有帮助,但前提是处理了无效的跨线程操作。我可以这样做:
private void btnHistory_Click(object sender, EventArgs e)
{
Class1 c = new Class1(ref label12);
c.StartClock();
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += ((s, e) =>
{
// time consuming function
Utility.PopulateHistory(dgvRecords_history, _util);
});
backgroundWorker.RunWorkerCompleted += ((s, e) =>
{
c.StopClock();
});
backgroundWorker.RunWorkerAsync();
}
在耗时函数运行的Utility
类中,
internal static void PopulateHistory(DataGridView dgv, Utility util)
{
SetDataGridView_History(dgv, util);
}
delegate void UpdateDataGridView_History(DataGridView dgv, Utility util);
static void SetDataGridView_History(DataGridView dgv, Utility util)
{
if (dgv.InvokeRequired)
{
UpdateDataGridView_History updaterDelegate = new UpdateDataGridView_History(SetDataGridView_History);
((Form)util._w).Invoke(updaterDelegate, new object[] { dgv, util });
}
else
//code that utilizes UI thread (long running process in my case)
}
感谢所有帮助过的人。我正在标记丹尼尔的回答..