简体   繁体   中英

How to control progress bar during executing async operations

during implementing exporting util for my project I encounter a problem with blocking UI during uploading files. Basically the problem is that during async task I'm not able to update progress bar.

I already tried a couple of solutions. In general when i call exportPopUp.ShowDialog() it blocks the execution of copyAttachment() and the whole logic is done after closing form. I decided to use Show() but when i does that the form is not alive (all grey)

Here is my background logic:

   private void exportButton_Click(object sender, EventArgs e)
    {
        // get files
        int row = reportsDataGrid.CurrentCell.RowIndex;
        if (row >= 0)
        {
            string problemId = reportsDataGrid.Rows[row].Cells[0].Value.ToString();
            AC.Trace.I("Problem Id", problemId);
            FolderBrowserDialog dlgFolderBrowser = new FolderBrowserDialog();
            dlgFolderBrowser.Description = "Select folder to save Report files!";
            DialogResult result = dlgFolderBrowser.ShowDialog();
            if (result == DialogResult.OK)
            {
                string folderName = dlgFolderBrowser.SelectedPath;
                AC.Trace.I("Destination folder name", folderName);
                CIS.PRS.Data.Attachments attachments = jampPrsService.ReportFiles(problemId);
                processAttachments(attachments, folderName, problemId);
            }
        }
    }

    private async void processAttachments(Attachments attachments, string folderName, string problemId)
    {
        this.exportPath = folderName + "\\" + problemId;
        cts = new CancellationTokenSource();
        this.exportPopUp = new exportPopUp(attachments.Size(), cts);
        this.exportPopUp.ExportFinished += ExportPopUp_ExportFinished;
        exportPopUp.setExportLabelText("Exporting problem report " + problemId);
        exportPopUp.ShowDialog();
        await copyAttachments(attachments, folderName, problemId);
    }

    private void ExportPopUp_ExportFinished()
    {
        this.finishExport();
    }

    private async Task copyAttachments(Attachments attachments, string folderName, string problemId)
    {
        //List<Task> tasks = new List<Task>();
        foreach (Attachment attachment in attachments.attachments)
        {
            //tasks.Add(Task.Factory.StartNew(() => copy(attachment, folderName, problemId)));
            await Task.Factory.StartNew(() => copy(attachment, folderName, problemId));
        }
        //await Task.WhenAll(tasks);
    }

    private void copy(Attachment attachment, string folderName, string problemId)
    {
        FileStream fs = null;
        if (!Directory.Exists(exportPath))
        {
            Directory.CreateDirectory(exportPath);
        }
        try
        {
            using (fs = new FileStream(Path.Combine(exportPath, attachment.Name), FileMode.Create))
            {
                fs.WriteAsync(attachment.Data, 0, attachment.Data.Length, this.cts.Token).Wait();
                fs.Flush();
                fs.Close();
                this.exportPopUp.performProgressBarStep();
            }
            AC.Trace.I("File has been saved: ", attachment.Name);
        }

        catch (Exception ex)
        {
            AC.Trace.E("Cannot write file " + attachment.Name, ex);
        }
    }
    private void finishExport()
    {
        this.exportPopUp.Close();
        this.exportPopUp.Dispose();
        MessageBoxCc.ShowInformation("Problem report exported succesfully. \n" +
                "Report exported to '"+ exportPath + "'", "Problem Request", "675");
    }
}

Here is my exportPopUp class:

public delegate void ExportFinishHandler();

    public partial class exportPopUp : Form
    {
        public event ExportFinishHandler ExportFinished;

        private CancellationTokenSource cancellationTokenSource;

        public exportPopUp(int progressBarSize, CancellationTokenSource cancellationTokenSource)
        {
            InitializeComponent();
            this.CenterToScreen();
            this.cancellationTokenSource = cancellationTokenSource;
            this.progressBar.Maximum = progressBarSize;
            this.progressBar.Step = 1;
            this.progressBar.Value = 0;        
        }

        public void setExportLabelText(string text)
        {
            exportLabel.Text = text;
        }

        public void performProgressBarStep()
        {
            this.progressBar.PerformStep();
            MessageBoxCc.ShowInformation("VALUE " + this.progressBar.Value + " MAX " + this.progressBar.Maximum, "KOZA", "123");
            if(this.progressBar.Value == this.progressBar.Maximum)
            {
                this.ExportFinished();
            }
        }

        private void cancelBtn_Click(object sender, EventArgs e)
        {
            cancellationTokenSource.Cancel();
        }
    }

Generally the whole logic works as I expected, but I'm not able to do copy tasks and update progress bar at same time. Thanks in advance

UPDATE

After changes its working as expected but for calling export not form export button its grey and stock again.

Im attaching execution of this method not from export button

Listener class:

// Inner listener class 
public class ReportCreatedListener
{
    private frameProblemRequestReport frameProblemRequestReport;

    public ReportCreatedListener(frameProblemRequestReport frameProblemRequestReport)
    {
        this.frameProblemRequestReport = frameProblemRequestReport;
    }

    public async Task notifyRaportCreated(string problemId)
    {
        await this.frameProblemRequestReport.reportCreationFinished(problemId);
    }
}

The call:

    internal async Task reportCreationFinished(string lastProblemId)
    {
        if ((lastProblemId).Contains(report.ReportInfo.ProblemId))
        {
            string problemId = report.ReportInfo.ProblemId;
            string folderName = "C:\\Users\\Z006DQF6\\Desktop";
            AC.Trace.I("Exporting created raport to location: ", folderName);
            CIS.PRS.Data.Attachments attachments = jampPrsService.ReportFiles(lastProblemId);
            await processAttachments(attachments, folderName, problemId);
        }
    }

reportCreationFinished is triggered from another listener

    private class StateListener : CompoundStateListener
    {
        private JAMPPRSService service;
        public StateListener(JAMPPRSService service)
        {
            this.service = service;
        }
        public async void stateChanged(CompoundModel cm)
        {
            string lastSendReportId = cm.getMember("LastCreatedReportId").getValue().ToString();
            await service.reportCreatedListener.notifyRaportCreated(lastSendReportId);
        }
    }

I'm not able go any higher because this event is coming from backend written in java

The problem is here the switching from asynchronous processing to synchronous. You do it even twice in your code.

If you start with async await, you need to draw it through the entire calling hierarchy.

1) start with the click handler. This should be the only async void method in the hierarchy. Await here the next one

private async void exportButton_Click(object sender, EventArgs e)
{
    await processAttachments(attachments, folderName, problemId);
}

2) make the next called method to return a Task and use Show so that copyAttachments will be executed afterwards and can be awaited

          return Task here
                |
                v
private async Task processAttachments(Attachments attachments, string folderName, string problemId)
{
    this.exportPath = folderName + "\\" + problemId;
    cts = new CancellationTokenSource();
    this.exportPopUp = new exportPopUp(attachments.Size(), cts);
    this.exportPopUp.ExportFinished += ExportPopUp_ExportFinished;
    exportPopUp.setExportLabelText("Exporting problem report " + problemId);
    exportPopUp.Show();  // <= !
    await copyAttachments(attachments, folderName, problemId);
}

3) use the Task that is returned from fs.WriteAsync and await it. Make the copy method again return a Task to propagate it upwards:

private void copy(Attachment attachment, string folderName, string problemId)
{
    ...
    try
    {
        using (fs = new FileStream(Path.Combine(exportPath, attachment.Name), FileMode.Create))
        {
            awaitfs.WriteAsync(attachment.Data, 0, attachment.Data.Length, this.cts.Token);
            fs.Flush();
            fs.Close();
            this.exportPopUp.performProgressBarStep();
        }           
    }
    ...

4) await the copy method (if you want to copy the attachments one after the other):

private async Task copyAttachments(Attachments attachments, string folderName, string problemId)
{
    foreach (Attachment attachment in attachments.attachments)
    {            
        await copy(attachment, folderName, problemId));
    }
}

This should yield a working solution, in which both Forms will stay responsive and you will see the progress bar filling up.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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