【问题标题】:C# How does a background thread tell a UI thread that it has finished doing something?C# 后台线程如何告诉 UI 线程它已经完成了某事?
【发布时间】:2010-12-13 15:35:47
【问题描述】:

场景

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

问题

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

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:

    如果您不使用background worker 线程(无论出于何种原因),那么您必须从您的线程中触发一个由 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。

    【讨论】:

      【解决方案2】:

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

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

      这里有更多信息: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

      【讨论】:

        【解决方案3】:

        您可以使用BackgroundWorker 在其 DoWork 事件处理程序中进行耗时的处理。然后处理 RunWorkerComplete 事件——它会在 DoWork 方法完成时触发。在这一切进行的同时,您的 UI 线程将愉快地运行。

        【讨论】:

        • 下次我会打字更快。 :)
        【解决方案4】:

        如果您使用的是 .NET 2.0 或更新版本,那么使用 BackgroundWorker 线程会更容易。它有自己的 RunWorkerCompleted 事件,可以满足您的需要。

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

        【讨论】:

          【解决方案5】:

          尝试使用 BackgrounWorker 并向其 RunWorkerCompleted 事件注册一个处理程序。

          【讨论】:

            【解决方案6】:

            在 Winforms 中,您可以使用 .Invoke 方法(并检查 .InvokeRequired 属性)将回调编组到 UI 线程。您不会过多地通知 UI 线程 - 它会继续运行并且不会等待任何类型的完成,但您可以使用来自另一个线程的控件进行交互(例如,更新标签的文本属性)调用方法。

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

            【讨论】:

              【解决方案7】:

              如果您谈论的是 WinForm 应用程序,您可以使用表单(或表单上的任何控件)上的 Invoke 方法更改任何 UI 对象。您还可以找到有用的 InvokeRequired 属性

              【讨论】:

                【解决方案8】:

                您可以使用 Dispatcher.CurrentDispatcher 存储对 UI 线程 Dispatcher 的引用(显然在 GUI 线程调用的方法中)。使用此对象,您可以在工作线程中使用 BeginInvoke 或 Invoke 方法在 GUI 线程上执行一个方法,通知它您已完成工作。就我个人而言,我发现这种方法比使用后台工作对象更灵活一些,并且可以生成更易读的代码。

                【讨论】:

                  【解决方案9】:

                  有一种在 C# 中使用多线程的简单方法。它被称为BackgroundWorker。 你应该看看:BackgroundWorker Tutorial

                  【讨论】:

                    【解决方案10】:

                    正如多次提到的,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.
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2017-05-15
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-01-30
                      • 2015-06-29
                      • 1970-01-01
                      相关资源
                      最近更新 更多