Click here to Skip to main content
15,887,027 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I was trying to update status on UI for a Long Running Operating. I've created a demo form based application, task it have multiple rows, each row is having days and default values in each column of datagrid is 0, once computation file computes one iteration for one day it will update UI and set 1 for that day.

I am using threading, delegates and events to implement this and it is working as expected if I put Thread.Sleep(100) between two event calls. If I put Thread.Sleep(100) inside last nested for loop then it updates UI as expected but as soon as I remove it and run loop without sleep, then it skips some of the columns on UI and directly update last few/random columns, as you can see in attached image link(Image of output of my code without thread sleep) only last column is getting updated..

If I am not mistaken all the events are getting fired in sequence then they should update UI in sequence too but it's not happening and I don't know why. I don't want to do this Sleep thing because I have around 14 calls in actual application for UI status update and it will run under a loop so if It put sleep(100) then it will cost me a lot, is there any way to do it without SLEEP?

Computation file:
C#
public class Class1 : IGenerate
{
    public event MessageEventHandler OnMessageSending;
    public void LongOperationMethod(BindingList<Status> _statusData)
    {
        if (OnMessageSending != null)
        {
            MessageEventArgs me = new MessageEventArgs();

            /// Loop for Weeks
            for (int k = 0; k < 2; ++k)
            {
                /// Loop for each day
                for (int i = 0; i < 7; ++i)
                {
                    /// Calculation on everyday
                    for (int j = 0; j < 1000; ++j)
                    {
                        // to do
                    }

                    me.weekNo = k;
                    me.DayNo = i;
                    OnMessageSending(me);

                }
            }
            me.Message = "Process completed successfully...";
            OnMessageSending(me);
        }
        else
        {
            throw new ArgumentException("Event hasn`t been rised, so we cannot continue working.");
        }
    }
}

UI file:
C#
public partial class Form1 : Form
{
    BindingList<Status> _statusData = new BindingList<Status>();
    delegate void StringParameterDelegate(string value);
    Class1 cls = new Class1();
    public Form1()
    {
        InitializeComponent();
        labelProgress.Text = "";
    }

    private void button1_Click_1(object sender, EventArgs e)
    {
        for (int i = 1; i <= 2; ++i)
        {
            _statusData.Add(new Status { Week = "Week" + i, Day1 = 0, Day2 = 0, Day3 = 0, Day4 = 0, Day5 = 0, Day6 = 0, Day7 = 0 });
        }

        dataGridView1.DataSource = _statusData;
     }

    private void button2_Click(object sender, EventArgs e)
    {
        Thread t1 = new Thread(() => StartingThread(_statusData));
        t1.Start();
    }

    void StartingThread(BindingList<Status> _statusData)
    {
        IGenerate generate = new Class1();
        generate.OnMessageSending += new MessageEventHandler(generate_OnMessageSending);
        generate.LongOperationMethod(_statusData);
    }

    private void generate_OnMessageSending(MessageEventArgs e)
    {
        int weekNo = e.weekNo;
        int dayNo = e.DayNo;
        this.dataGridView1.BeginInvoke(new MethodInvoker(() => dataGridView1.Rows[e.weekNo].Cells[e.DayNo + 1].Value = 1));
        this.labelProgress.BeginInvoke(new MethodInvoker(() => this.labelProgress.Text = e.Message));
     
    }
}
Posted
Updated 29-Nov-15 8:41am
v4

1 solution

I think the problem is that you when you pass the event argument, you don't pass it is a value but as a reference. See Parameter passing in C#[^]

As you only create one instance of MessageEventArgs me = new MessageEventArgs();, you only have one place in the memory where your data is stored.
This means that when you change a member in that instance, that change will be reflected also in the event method.
The reason the sleep works is because you slow down the process enough for the values to be printed in the data grid before they are changed in the loop.

You should add one or more constructors with parameters to your MessageEventArgs class.
For example:
C#
public MessageEventArgs(int weekNo, int dayNo)
{
    this.WeekNo = weekNo;
    this.DayNo = dayNo;
}


That way it is easy to create a new instance of the event argument for every message.
C#
public void LongOperationMethod(BindingList<status> _statusData)
{
    if (OnMessageSending != null)
    {
        /// Loop for Weeks
        for (int k = 0; k < 2; ++k)
        {
            /// Loop for each day
            for (int i = 0; i < 7; ++i)
            {
                /// Calculation on everyday
                for (int j = 0; j < 1000; ++j)
                {
                    // to do
                }

                OnMessageSending(new MessageEventArgs(k, i));

            }
        }
        OnMessageSending(new MessageEventArgs("Process completed successfully..."));
    }
    else
    {
        throw new ArgumentException("Event hasn`t been rised, so we cannot continue working.");
    }
}
</status>
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900