【问题标题】:How do I report progress to the GUI from a list of Tasks?如何从任务列表向 GUI 报告进度?
【发布时间】:2015-04-17 22:24:57
【问题描述】:

我目前正在开发一个将文件列表从 .ps (PostScript) 转换为 .png 的程序。

最初,这是在一个批处理文件中完成的,一次一个文件。我正在编写使用 Ghostscript.NET dll 异步处理这些文件的代码。通过将这些拆分为任务,我将处理时间从 30 分钟缩短到了大约 6 分钟。

我希望能够向用户展示这方面的一些进展,这样我的程序就不会看起来像被冻结了。

我对线程的了解足以让自己感到沮丧,因此非常感谢有关最佳方法的任何建议。下面的代码实现了一个 BackgroundWorker 来尝试显示进度。我以前用过 BGWorker 来显示进度,但不是在这样的多个任务上。事实上,这是我第一次不只使用 BGWorker 的多线程。

我觉得 BGWorker 可能不是我需要使用的,但我想在我问之前尝试自己尝试一下。

这是我目前的代码:

public partial class ProcessStatusForm : Form
{
    public string[] testList;
    public string wordPath;
    public string StatusText;
    public GhostscriptVersionInfo _gs_version_info;
    public DirectoryInfo dInfo;
    public List<Task> tasks;
    public float NumberOfTasks;
    public bool PS2PNGRunning;
    public int ProgressPct;
    public float dPercent;
    public decimal decPercent;

    public ProcessStatusForm(string wordDoc, List<string> runList)
    {
        InitializeComponent();
        this.wordPath = wordDoc;
        this.testList = runList.ToArray();
        this.StatusText = string.Empty;
        this._gs_version_info = GhostscriptVersionInfo.GetLastInstalledVersion(GhostscriptLicense.GPL |
            GhostscriptLicense.AFPL, GhostscriptLicense.GPL);
        this.dInfo = new DirectoryInfo(SettingsClass.PSFolder);
        this.PS2PNGRunning = false;
        this.ProgressPct = 0;
        this.NumberOfTasks = runList.Count;
    }

    private void ProcessStatusForm_Shown(object sender, EventArgs e)
    {
        //Spawn tasks for each of the .ps files in the PS_FILES folder
        tasks = new List<Task>(dInfo.GetFiles("*.ps").Length);

        //Start the BackgroundWorker
        this.PS2PNGRunning = true;
        BackgroundWorker.RunWorkerAsync();

        foreach (var file in dInfo.GetFiles("*.ps"))
        {
            //Get fileName to pass fo the ConvertPS2PNG
            string inputFile = file.Name;

            //Create the Task
            var task = Task.Factory.StartNew(() => ConvertPS2PNG(inputFile));
            tasks.Add(task);
        }
        //Wait until all tasks have completed
        Task.WaitAll(tasks.ToArray());
        PS2PNGRunning = false;
    }

    private void ConvertPS2PNG(string input)
    {
        string output = input.Replace(".ps", "_01.png");
        input = SettingsClass.PSFolder + input;
        output = SettingsClass.PNGFolder + output;
        GhostscriptProcessor processor = new GhostscriptProcessor(_gs_version_info, true);
        processor.Process(CreateGSArgs(input, output), new ConsoleStdIO(true, true, true));
    }

    private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        ProgressPct = 0;
        while (PS2PNGRunning)
        {
            Thread.Sleep(1000);
            float TasksCompleted = 0;
            foreach (var tsk in tasks)
            {
                if (tsk.Status == TaskStatus.RanToCompletion)
                {
                    TasksCompleted++;
                }
            }
            StatusText = TasksCompleted + " of " + NumberOfTasks + " converted...";
            dPercent = TasksCompleted / NumberOfTasks;
            dPercent *= 100;
            decPercent = (decimal)dPercent;
            decPercent = Math.Round(decPercent);
            ProgressPct = (int)decPercent;
            BackgroundWorker.ReportProgress(ProgressPct);
        }
        BackgroundWorker.ReportProgress(100);
    }

    private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.ProgressLabel.Text = this.StatusText;
        this.progressBar.Style = ProgressBarStyle.Continuous;
        this.progressBar.Value = e.ProgressPercentage;
    }

    public string[] CreateGSArgs(string inPath, string outPath)
    {
        List<string> gsArgs = new List<string>();

        gsArgs.Add("-dBATCH");
        gsArgs.Add("-dNOPAUSE");
        gsArgs.Add("-sDEVICE=png16m");
        gsArgs.Add("-dQUIET");
        gsArgs.Add("-sPAPERSIZE=letter");
        gsArgs.Add("-r800");
        gsArgs.Add("-sOutputFile=" + outPath);
        gsArgs.Add(inPath);

        return gsArgs.ToArray();
    }
}

当我在 BackgroundWorker_DoWork 的代码中添加中断时,似乎一切正常,但是当它到达 BackgroundWorker.ReportProgress() 时,它永远不会到达 BackgroundWorker_ProgressChanged() 方法。

至少,我可以忍受在运行时将 progressBar.Style 作为选框,以便用户可以看到程序正在运行,但报告实际进度将是理想的。

正如我之前所说,我没有在线程方面做大量工作,我在这个主题上的所有知识几乎都来自 Google 和 StackOverflow。如果有完全不同的方法可以做到这一点,我愿意接受所有批评。

【问题讨论】:

  • 您的 UI 使用的是什么?您使用的是 Winforms 还是 WPF?
  • 我只是为此使用 WinForms。如果有更好或更简单的方法可以使用 WPF,我可以使用 WPF,因为我刚开始开发它。
  • 您可能希望设置一个调度计时器来保持 UI 更新而不是该事件。要么那个,要么事件可能没有被引发,所以你可以尝试从窗口的调度程序中对进度等进行更改:this.Dispatcher.Invoke(new Action(() =&gt; {"Do work here""})); 这应该会引发对 UI 的这些更改。
  • 我以前从未使用过 Dispatcher。我需要更改为 WPF 才能使用它吗?我在该主题上看到的所有材料似乎都是通过 WPF 完成的。
  • 它通常在 WPF 和 WPF 的上下文中使用,在我看来,它确实提供了比 Winforms 更大的灵活性,但 Dispatcher 的使用应该独立于 WPF 窗口。对 Dispatcher 做一些研究,文档将提供大量更详细的信息。

标签: c# multithreading task-parallel-library task ghostscript.net


【解决方案1】:

当您从设计器屏幕拖动对象时,是否为该对象指定了名称 BackgroundWorker?如果不更改您的代码以使用给定的适当名称(默认应该是 backgroundWorker1)。

或者……

尝试在 DoWork 方法中将 sender 对象转换为 BackgroundWorker 对象,然后从那里调用 ReportProgress()。

private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker bw = sender as BackgroundWorker;
    if (bw != null)
    {
        bw.ReportProgress(25);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多