Report progress for interface of an asynchronous method in C #

5

I have a Windows Forms application. This application will run some processes that take a while to run, so I would like to run them in parallel. Here's a little of what I'm trying to implement.

In the form constructor, I create a generic list ( ProcessViewModel ) of objects called Processes , with some information pertinent to each execution.

private List<ProcessViewModel> Processes { get; set; }

public Form1()
{
    InitializeComponent();

    Processes  = new List<ProcessViewModel>();
    for(int i = 0; i < 10; i++)
    {
       Processes.Add(new ProcessViewModel() 
                         {  
                            Id = i, 
                            Process = "Process " + i,
                            Status = "Stopped", 
                            Progress = 0,
                            Max = 3000
                         });
    }
}

When the user clicks a button, I will start about 10 processes using the ProcessObject method, which in turn will perform heavy processing (various statistical calculations). So I go through this list of processes and review as an argument to an asynchronous method.

private async void button1_Click(object sender, EventArgs e)
{
    // esta lista contém 10 objetos que servem como parametros para cada processo
    foreach (var process in Processes)
        await ProcessObject(process);
}

In my asynchronous method I get the argument and I will perform this operation that contains a loop and in this loop (my processing) and would like to notify the progress of this processing to an interface.

private async Task ProcessObject(ProcessViewModel process)
{
    process.Status = "Starting";
    await Task.Run(()=>
    {
       process.Progress = 0;
       do
       {
            process.Status = "Running";

           // aqui entra meu processamento pesado

           // gostaria de atualizar meu objeto aqui
           process.Progress++;

           // feedback para interface
           UpdateRow(process);                

       } while (process.Progress < process.Max && /*outra condição estatística*/);

    });    
}

As I will have 10 process operants at the same time, I thought of doing a grid, as in the image below:

This is the method that updates a grid with the progress information of each activity.

private void UpdateRow(ProcessViewModel process)
{
    dataGridView1.Rows[process.Index - 1].Cells[1].Value = process.Progress;
    dataGridView1.Refresh();
}

It turns out that when it comes time to update the interface, I'm getting an exception of type InvalidOperationException .

  

Is there any way to perform heavy processing and send a   asynchronously to an IU without interrupting the   processing?

I know that in .Net, there are several resources for concurrent programming like Threads , Parallels and async/await , Task , but I'm not sure which one to use so that my application can scale the best way. I do not know if what I'm doing is the best way.

    
asked by anonymous 09.09.2014 / 19:53

1 answer

5

The exception in question occurs because Windows Forms does not allow you to change the controls of your Form while running from a thread that is not the GUI thread.

To resolve this issue, you can do one of the following:

1. Progress Reporting , with an implementation of IProgress < T > > , for .NET 4.5.

This interface basically allows you to tell when there has been a change in your asynchronous method, allowing any changes to be made within the GUI thread.

In your example, your asynchronous method would have to be changed:

private async Task ProcessObject(ProcessViewModel process, IProgress<ProcessViewModel> progress)
{
    // início do método...

    // avisa a interface
    progress.Report(process);

    // resto do método...       
}

When the Report () method is run, the progress object will run the ProgressChanged event, where you should update the graphical interface:

var progress = new Progress<ProcessViewModel>();
progress.ProgressChanged += progress_ProgressChanged;

void progress_ProgressChanged(object sender, ProcessViewModel e)
{
    UpdateRow(e);
}

2. The class SynchronizationContext , from .NET 2.0

Using the Post and Send () methods, you can execute portions of code in the context of a different thread than the current thread.

In this way, you would be able to make your calls to controls within your own asynchronous method. An example:

public Form1() 
{
    InitializeComponent();
    m_SynchronizationContext = SynchronizationContext.Current;
}

private SynchronizationContext m_SynchronizationContext;

And within your asynchronous method:

private async Task ProcessObject(ProcessViewModel process)
{
    // início do método...

    // atualiza a interface
    m_SynchronizationContext.Post((@object) => 
    {
        UpdateRow(@object);
    }, process);

    // resto do método...       
}
  

I know that in .Net, there are several features for concurrent programming like Threads, Parallels and async / await, Task, but I'm not sure which one to use so that my application can scale in the best way. I do not know if what I'm doing is the best way.

In this case, it all depends on the flow of execution of your threads.

By its example, the async / await of the click of the button will allow the Form to continue with the free thread for interaction while its processing takes place, however await within the foreach indicates that the program should wait for the end of processing to proceed in the loop iteration. Thus, the processing will occur sequentially.

If the processes are independent and can be run in parallel, try using some of what the Task Parallel Library offers:

var listTasks = new List<Task>();

foreach (var process in Processes)
{
    listTasks.Add(ProcessObject(process, progress));
}

await Task.WhenAll(listTasks.ToArray());

In this way, the program will assign a thread for each execution of the ProcessObject () method and the Task.WhenAll () method will force wait for all processing to complete.

    
10.09.2014 / 23:44