方案
假设你有一个C# WinForms应用程序,正在进行一些数据处理。你有一个从数据库中检索数据的方法,该方法由UI线程调用。后台线程然后运行此任务。你希望UI继续执行其任务而不被锁定和无响应。
问题
如何让后台线程运行并执行其处理,然后在其返回结果时自动通知UI线程?
假设你有一个C# WinForms应用程序,正在进行一些数据处理。你有一个从数据库中检索数据的方法,该方法由UI线程调用。后台线程然后运行此任务。你希望UI继续执行其任务而不被锁定和无响应。
如何让后台线程运行并执行其处理,然后在其返回结果时自动通知UI线程?
public void Build()
{
FindAlbums(Root);
// Final update
if (Library_Finished != null)
{
Library_Finished(this, null);
}
}
private void FindAlbums(string root)
{
// Find all the albums
string[] folders = Directory.GetDirectories(root);
foreach (string folder in folders)
{
string[] files = Directory.GetFiles(folder, "*.mp3");
if (files.Length > 0)
{
// Add to library - use first file as being representative of the whole album
var info = new AlbumInfo(files[0]);
if (Library_AlbumAdded != null)
{
Library_AlbumAdded(this, new AlbumInfoEventArgs(info));
}
}
FindAlbums(folder);
}
}
然后在UI线程中(这是WinForms代码):
private void Library_AlbumAdded(object sender, AlbumInfoEventArgs e)
{
if (dataGridView.InvokeRequired)
{
dataGridView.Invoke((MethodInvoker)delegate { AddToGrid(e.AlbumInfo); });
}
else
{
AddToGrid(e.AlbumInfo);
}
}
private void Library_Finished(object sender, EventArgs e)
{
if (dataGridView.InvokeRequired)
{
dataGridView.Invoke((MethodInvoker)delegate { FinalUpdate(); });
}
else
{
FinalUpdate();
}
}
然而,我建议您调查后台工作线程,因为它可以为您处理很多后勤工作。但是,在RunWorkerCompleted
事件中需要相同的处理代码来更新用户界面。
有几种方法可以做到这一点,但最简单的方法是使用BackgroundWorker。
它基本上有两个委托,DoWork和WorkCompleted。 DoWork在单独的线程上执行,而WorkCompleted回调发生在UI线程上。
这里有更多信息: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
正如多次提到的那样,可以使用BackgroundWorker类。
或者,您可以执行类似以下操作的操作:
void buttonGo_Clicked( object sender, EventArgs e )
{
MyAsyncClass class = new MyAsyncClass();
class.LongOperationFinished += (LongOperationFinishedEventHandler)finished;
class.BeginLongOperation();
}
void finished( object sender, EventArgs e )
{
if( this.InvokeRequired ) {
this.BeginInvoke( (LongOperationFinishedEventHandler)finished, sender, e );
return;
}
// You can safely modify the gui here.
}
如果您正在使用.NET 2.0或更高版本,则可以使用BackgroundWorker线程轻松实现此目的。它有自己的RunWorkerCompleted事件,正好可以满足您的需求。
事实上,我强烈推荐使用BackgroundWorker。它具有大多数开发人员在创建线程时需要的功能。它们也更容易优雅地取消,并且甚至具有报告进度的能力。
你可以使用Dispatcher.CurrentDispatcher在GUI线程调用的方法中存储对UI线程Dispatcher的引用。使用此对象,您可以在工作线程中使用BeginInvoke或Invoke方法来执行一个方法,通知GUI线程您已完成工作。个人认为,这种方法比使用后台工作器对象稍微灵活一些,并且可以生成稍微更易读的代码。