一直以来都有一个疑问,既然只有一个UI线程,且非UI线程也不应该更新UI控件的状态,那我能不能做这么一个程序,就是画面上有一个文本区域,程序不停的往这个文本区域中写特定的内容,同时还能提供一个暂停的按钮,在按下暂停按钮时,程序可以被暂停。
其实不用考虑,这样的程序肯定可以做出来。看看你的杀毒软件就知道了。杀毒软件在扫描时,会不停的在画面上显示当前正在扫描的文件,同时还可以暂停扫描。而且,交互性很好。按下暂停按钮后就可以马上暂停住程序。
自己做了个小实验,一个画面上添加了两个按钮(Start,Stop)和一个文本框。按下Start时,不停的往文本框中写内容。
Start_Click()
{
while(!stop)
{
txbData.AppendText(DateTime.Now.ToString() + "/n");
}
}
Stop_Click()
{
stop = true;
}
结果发现,按下Stop按钮时,程序根本就没有反应。
后来考虑到用多线程来做。在非UI线程中使用Invoke来调用UI线程更新文本框内容。发现还是停不下来。
后来到网上搜搜,终于有了点眉目(网址 )。根本的玄机就在于,别把UI线程逼的太狠了。不要在循环体中不停的调用Invoke来改变控件状态,而是每次改变一点状态之后,调用一下sleep(),让当前线程让出CPU,交给UI去更新画面。
而且,也不用Invoke了,用BeginInvoke,异步操作。
下面是自己仿造上面网址的例子做了一个小例子,发现效果还不错:
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.Threading;
using System.IO;
namespace ImproveInteractive
{
public partial class Form1 : Form
{
private Thread t = null;
private string tempString = string.Empty;
private Stack<string> folders = new Stack<string>();
public Form1()
{
InitializeComponent();
}
private void btnStart_Click(object sender, EventArgs e)
{
if (btnStart.Text == "Start")
{
if (!Directory.Exists(this.txbFilePath.Text))
{
MessageBox.Show("Please set a valid *Directory*");
return;
}
btnStart.Text = "Pause";
btnStop.Enabled = true;
txbData.Clear();
folders.Push(this.txbFilePath.Text);
AbortThread(t);
t = new Thread(new ThreadStart(LoopShow));
t.Start();
}
else if (btnStart.Text == "Pause")
{
btnStart.Text = "Continue";
t.Suspend();
}
else if (btnStart.Text == "Continue")
{
btnStart.Text = "Pause";
t.Resume();
}
}
private void btnStop_Click(object sender, EventArgs e)
{
AbortThread(t);
btnStop.Enabled = false;
btnStart.Text = "Start";
}
private void AbortThread(Thread t)
{
if (t != null && t.IsAlive)
{
if (t.ThreadState == ThreadState.Suspended)
{
t.Resume();
}
t.Abort();
}
}
private void LoopShow()
{
while (folders.Count > 0)
{
string folder = folders.Pop();
if (folder.Contains("System Volume Information") || folder.Contains("RRbackups"))
{
continue;
}
foreach (string subFolder in Directory.GetDirectories(folder))
{
folders.Push(subFolder);
}
foreach (string file in Directory.GetFiles(folder))
{
tempString = file;
if (!this.IsDisposed)
{
this.BeginInvoke(new MethodInvoker(SetText));
Thread.Sleep(10);
}
}
}
}
private void SetText()
{
this.txbData.AppendText(tempString + "/n");
}
private void btnFolderChoose_Click(object sender, EventArgs e)
{
FolderBrowserDialog folderBroser = new FolderBrowserDialog();
DialogResult result = folderBroser.ShowDialog();
if (result == DialogResult.OK || result == DialogResult.Yes)
{
this.txbFilePath.Text = folderBroser.SelectedPath;
}
folderBroser.Dispose();
}
}
}
需要注意的地方,大概有三点:
- 使用异步操作更新控件后,Sleep一小段时间。10ms就够了。主要是这会让进程重新调度下,让UT线程得到执行。this.BeginInvoke(new MethodInvoker(SetText)); Thread.Sleep(10);
- 当点了关闭按钮,调用this.BeginInvoke时,非UI线程可能会抱错。所以要加个判断if (!this.IsDisposed)
- 文中使用的suspend,resume 已经不丢弃了,因为suspend可能会引起线程死锁。有个好同志给了一个替代方案,以后可以参考下(网址 )