在主UI中循环调用的后台线程完成时更新UI

4
我有一个WinForms应用程序,它调用一个业务类方法执行某些耗时的操作,每次调用需要约5秒钟。主窗体在循环中调用此方法。该循环可以运行10次到10000次不等。
WinForms应用程序向业务类发送参数,并有一个区域显示每个方法调用所花费的时间以及方法返回的值。如何通知主窗口并更新主Winform中的文本区域,以显示每个调用所返回的内容?
目前所有数据都在所有线程完成后一次性传输。是否有一种方法可以在每个调用完成后更新循环的所有迭代的UI?我也不介意按顺序进行更新。
窗体:
    HeavyDutyClass hd;
    public Form1()
    {
        InitializeComponent();
        hd = new HeavyDutyClass();
    }


    //BUTTON CLICK
    private void Start_Click(object sender, EventArgs e)
    {

        int filecount = 5000;  //BAD - opening 5000 threads! Any other approach?
        hd.FileProcessed += new EventHandler(hd_FileProcessed);


        var threads = new Thread[filecount];

        for (int i = 0; i < filecount; i++)
        {
            threads[i] = new Thread(() => { hd.LongRunningMethod(); });
            threads[i].Start();   
        }

    }

    //BUSINESS CLASS EVENT THAT FIRES WHEN BUSINESS METHOD COMPELTES
    void hd_FileProcessed(object sender, EventArgs e)
    {

        if (dgv.InvokeRequired)
        {
            dgv.Invoke((MethodInvoker)delegate { UpdateGrid(); });

        }
    }

    private void UpdateGrid()
    {
        dgv.Rows.Add(1);
        int i = dgv.Rows.Count;
        dgv.Rows [ i-1].Selected = true;
        dgv.FirstDisplayedScrollingRowIndex = i - 1;

    }

业务 HeavyDuty 级别
    public event EventHandler FileProcessed;

    public HeavyDutyClass()
    {
    }

    protected virtual void OnMyEvent(EventArgs e)
    {
        if (FileProcessed != null)
        {
            FileProcessed(this, e);
        }
    }

    public bool LongRunningMethod()
    {
        for (double i = 0; i < 199990000; i++)
        {
            //time consuming loop
        }
        OnMyEvent(EventArgs.Empty);
        return true;
    }

为什么不使用BackgroundWorker并使用ProgressChanged处理程序(ReportProgress方法)来实现你想做的事情呢?Background Worker就是为这种单个长时间运行的后台任务而设计的。你可以搜索并找到很多相关示例。 - Angshuman Agarwal
我可以在循环中调用它大约5000次吗? - user20358
可以的。已更新答案。 - Angshuman Agarwal
1个回答

8
添加一个Winforms项目,在窗体上放置一个标签控件,复制粘贴以下代码并按F5键即可运行。
[编辑]: 根据用户的意见,更新了业务类的注释。
注意:我的窗体类名为Form3。您可能需要更改您的Program.cs或反之亦然。
using System.ComponentModel;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public class BusinessClass
    {
        public int MyFunction(int input)
        {
            return input+10;
        }
    }

    public partial class Form3 : Form
    {
        private BackgroundWorker _worker;
        BusinessClass _biz = new BusinessClass();
        public Form3()
        {
            InitializeComponent();
            InitWorker();
        }

        private void InitWorker()
        {
            if (_worker != null)
            {
                _worker.Dispose();
            }

            _worker = new BackgroundWorker
            {
                WorkerReportsProgress = true,
                WorkerSupportsCancellation = true
            };
            _worker.DoWork += DoWork;
            _worker.RunWorkerCompleted += RunWorkerCompleted;
            _worker.ProgressChanged += ProgressChanged;
            _worker.RunWorkerAsync();
        }


        void DoWork(object sender, DoWorkEventArgs e)
        {
            int highestPercentageReached = 0;
            if (_worker.CancellationPending)
            {
                e.Cancel = true;
            }
            else
            {
                double i = 0.0d;
                int junk = 0;
                for (i = 0; i <= 199990000; i++)
                {
                    int result = _biz.MyFunction(junk);
                    junk++;

                    // Report progress as a percentage of the total task.
                    var percentComplete = (int)(i / 199990000 * 100);
                    if (percentComplete > highestPercentageReached)
                    {
                        highestPercentageReached = percentComplete;
                        // note I can pass the business class result also and display the same in the LABEL  
                        _worker.ReportProgress(percentComplete, result);
                        _worker.CancelAsync();
                    }
                }

            }
        }

        void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                // Display some message to the user that task has been
                // cancelled
            }
            else if (e.Error != null)
            {
                // Do something with the error
            }
        }

        void ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            label1.Text =  string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
        }
    }
}

使用这种方法,您也可以非常容易地实现取消功能。请注意,在初始化期间,我设置了 WorkerSupportsCancellation = true,然后我在 DoWork 中检查 _worker.CancellationPending。所以,如果您想通过单击“取消”按钮来取消进程,则应在按钮处理程序中编写此代码:_worker.CancelAsync();


谢谢。这个能支持向我的业务类方法传递参数并从中获取返回值吗?我想我需要在DoWork中的for循环内调用我的业务方法。 - user20358
是的,你可以。你只需要在我进行for循环的地方调用你的业务方法并传入参数,或者换句话说,在DoWork()的else块中编写自己耗时的逻辑即可。 - Angshuman Agarwal
注:在DoWork()中我也可以像这样传递业务类的结果:_worker.ReportProgress(percentComplete, result);,随后在标签上显示同样的内容[请参见ProgressChanged]。您可以使用新代码再次运行,并查看结果。 - Angshuman Agarwal
谢谢。我发帖后很快就解决了。:) 无论如何,还是非常感谢您的帮助。 - user20358
DoWork 方法中的 CancelAsync 调用看起来不太合适。此外,根据我的阅读,for 循环应该包括类似以下的内容: if (backgroundWorker.CancellationPending) { e.Cancel = true; return; } - Mark Miller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接