Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to provide a feedback to UI in a async method?

I have been developing a windows forms project where I have a 10 tasks to do, and I would like to do this in a async way. These tasks will star when the user click in a button and I call a async method to do this. In my code, I already have a list of parameters for these processes.

My questions is:

A) How transform my code to run all process in parallel? (I would like to implement async/await)

B) How to provide a feedback to my UI application?

The code bellow is what I have tried:

My button to call a method to start processes

private void button1_Click(object sender, EventArgs e)
{
    // almost 15 process
    foreach (var process in Processes)
    {
        // call a async method to process
        ProcessObject(process);
    }
}

The method to simulate my process getting a parameter

private async void ProcessObject(ProcessViewModel process)
{
    // this is my loop scope, which I need to run in parallel
    {
       // my code is here

       // increment the progress of this process
       process.Progress++;

       // feedback to UI (accessing the UI controls)
       UpdateRow(process);
    }
}

I tried this, but I am not sure if it is the right way to update my UI (a grid).

private void UpdateRow(ProcessViewModel process)
{
    dataGridView1.Rows[process.Index - 1].Cells[1].Value = process.Progress;
    dataGridView1.Refresh();
}
like image 526
Felipe Oriani Avatar asked Sep 09 '14 12:09

Felipe Oriani


2 Answers

Sriram covered how to do the async/await in his answer, however I wanted to show you another way to update the progress on the UI thread using his answer as a base.

.NET 4.5 added the IProgress<T> interface and the Progress<T> class. The built in Progress class captures the synchronization context, just like async/await does, then when you go to report your progress it uses that context to make its callback (the UI thread in your case).

private async void button1_Click(object sender, EventArgs e)
{
     var progress = new Progress<ProcessViewModel>(UpdateRow); //This makes a callback to UpdateRow when progress is reported.

     var tasks = Processes.Select(process => ProcessObject(process, progress)).ToList();
     await Task.WhenAll(tasks);
}

private async Task ProcessObject(ProcessViewModel process, IProgress<ProcessViewModel> progress)
{
    // my code is here with some loops
    await Task.Run(()=>
    {
        //Will be run in ThreadPool thread
        //Do whatever cpu bound work here


        //Still in the thread pool thread
        process.Progress++;

        // feedback to UI, calls UpdateRow on the UI thread.
        progress.Report(process); 
    });
}
like image 191
Scott Chamberlain Avatar answered Oct 17 '22 05:10

Scott Chamberlain


First of all, say no to async void methods (Event handlers are exceptions) because exceptions thrown inside it will not be noticed. Instead use async Task and await it.

private async void button1_Click(object sender, EventArgs e)// <--Note the async modifier
{
    // almost 15 process
    foreach (var process in Processes)
    {
        // call a async method to process
        await ProcessObject(process);
    }
}

private async Task ProcessObject(ProcessViewModel process)// <--Note the return type
{
    // my code is here with some loops
    await Task.Run(()=>
    {
        //Will be run in ThreadPool thread
        //Do whatever cpu bound work here
    });

    //At this point code runs in UI thread
    process.Progress++;

    // feedback to UI
    UpdateRow(process);
}

However, this will start only one task at a time, once that is done it will start next. If you want to start all of them at once you can start them and use Task.WhenAll to await it.

private async void button1_Click(object sender, EventArgs e)// <--Note the async modifier
{
     var tasks = Processes.Select(ProcessObject).ToList();
     await Task.WhenAll(tasks);
}
like image 28
Sriram Sakthivel Avatar answered Oct 17 '22 07:10

Sriram Sakthivel