Report progress to the interface of an asynchronous method in C#

Asked

Viewed 560 times

5

I have an app Windows Forms. This application will perform some processes that take some time to execute, so I would like to run them in parallel. Follows a little of what I’m trying to implement.

In the form builder, I create a generic list (ProcessViewModel) of objects called Processes, with some information relevant to each implementation.

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 through the method ProcessObject, which in turn will perform a heavy processing (several statistical calculations). So I go through this list of processes and pass it on 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 I 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 operating processes at the same time, I thought of doing a grid, as in the image below:

inserir a descrição da imagem aqui

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 the type InvalidOperationException.

Is there any way to perform a heavy processing and send a asynchronous feedback to an UI, without interrupting the processing?

I know that on . Net, there are several features for concurrent programming such as Threads, Parallels and async/await, Task, but I’m not sure which one to use so my app can climb the best way. I don’t know if what I’m doing is the best way.

  • What is this Process and consequently the process? "when it comes time to update the interface" is vague. Try to put more information to help understand the whole. The async seems to me to be a suitable solution, and really it needs to be used carefully with UI.

  • I’ll edit my question, thanks @bigown

  • Some helpful links: http://stackoverflow.com/questions/17972268/async-await-with-a-winforms-progressbar, http://simplygenius.net/Article/AncillaryAsyncProgress, http://blogs.msdn.com/b/dotnet/archive/2012/06/async-in-4-5-enabling-progressand-cancellation-in-async-Apis.aspx, http://www.codeproject.com/Tips/729674/Simple-Net-progress-bar-using-async-await, http://codereview.stackexchange.com/questions/20820/use-and-understanding-ofasync-awaitin-net-4-5, http://www.danderson.me/posts/async-and-awaitui-synchronization-with-c-5-net-4-5-and-winforms/

  • I changed my question, see if it was clearer what I’m trying rs. Thanks @bigown

  • One way would be using Delegates to update your UI. Have some knowledge about Delegates?

  • @Andrade, Yes, I usually work with Action<> and Func<>, but is there a way to implement this asynchronously? How could I?

Show 1 more comment

1 answer

6


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

To solve this problem, you can opt for one of the following options:

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

This interface basically allows you to warn when there has been some change in your asynchronous method, allowing any change to be executed within the graphical interface 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 method Report() is executed, the Progress object will execute the event Progresschanged, 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

By the methods Post() and Send(), you can execute portions of code in the context of a thread other than the one running the current thread.

This way, you could make your calls to the 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 such as Threads, Parallels and async/await, Task, but I’m not sure which one to use so my application can scale in the best way. I don’t know if what I’m doing is the best way.

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

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

If processes are independent and can be run in parallel, try to use some of what the Parallel Library provides:

var listTasks = new List<Task>();

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

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

Thus, the program will assign a thread for each execution of the Processobject() method and the method Task.Whenall() will force all processing to complete.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.