繁体   English   中英

C#后台线程如何告诉UI线程它已完成某件事?

[英]C# How does a background thread tell a UI thread that it has finished doing something?

情境

假设您有一个进行某些数据处理的C#WinForms应用程序。 您有一个方法可以从UI线程调用的数据库中检索数据。 然后,后台线程运行以执行此任务。 您希望UI继续执行其工作,而不是被锁定和无响应。

如何让后台线程运行并进行处理,然后在返回结果时自动提醒UI线程?

如果您不使用后台工作线程(出于某种原因),则必须从线程中触发由UI线程处理的事件。 例如,我有这段代码,它扫描我的mp3并为找到的每个专辑触发并触发事件,然后在完成(或停止)时再扫描另一个事件:

    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事件中将需要相同的处理代码来更新UI。

有几种方法可以做到这一点,但是最简单的方法是使用BackgroundWorker。

本质上,它具有两个委托,即DoWork和WorkCompleted。 DoWork在单独的线程上执行,而WorkCompleted回调在UI线程上发生。

这是更多信息: http : //msdn.microsoft.com/zh-cn/library/system.componentmodel.backgroundworker.aspx

您可以使用BackgroundWorker在其DoWork事件处理程序中进行耗时的处理。 然后处理RunWorkerComplete事件-DoWork方法完成后将触发该事件。 在进行所有这些操作的同时,您的UI线程将愉快地运行。

如果您正在谈论WinForm应用程序,则可以使用窗体(或窗体上的任何控件)上的Invoke方法对任何UI对象进行更改。 您还可以找到有用的InvokeRequired属性

如果您使用的是.NET 2.0或更高版本,则可以通过BackgroundWorker线程使此操作变得更加容易。 它具有自己的RunWorkerCompleted事件,该事件可以满足您的需求。

实际上,我强烈推荐BackgroundWorker。 它具有大多数开发人员在创建线程时所追求的功能。 它们也更容易优雅地取消,甚至可以报告进度。

尝试使用BackgrounWorker并将处理程序注册到其RunWorkerCompleted事件。

在Winforms中,可以使用.Invoke方法(并检查.InvokeRequired属性)来整理对UI线程的调用。 您不必过多地通知UI线程-它一直在继续,并且不等待任何形式的完成,但是您可以使用以下命令与另一个线程的控件进行交互(例如,更新标签的text属性)调用方法。

您还可以使用BackgroundWorker对象(请阅读MSDN以获得更多信息),该对象实现了回调功能,以在后台工作完成后在UI线程上运行一些代码。

您可以使用Dispatcher.CurrentDispatcher(显然是在GUI线程调用的方法中)存储对UI线程Dispatcher的引用。 使用该对象,您可以在工作线程中使用BeginInvoke或Invoke方法在GUI线程上执行方法,以通知您您已完成工作。 我个人认为此方法比使用后台工作程序对象稍微灵活一些,并且可以产生更具可读性的代码。

在C#中有一种使用多个线程的简单方法。 它称为BackgroundWorker。 您应该检查一下: BackgroundWorker教程

如很多次提到的,可以使用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.
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM