且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

循环中的触发事件不是按顺序更新UI

更新时间:2022-11-13 21:28:55

我认为问题是当你通过活动时参数,你不传递它是一个值,但作为参考。请参阅使用C#传递参数 [ ^ ]



因为你只创建一个 MessageEventArgs me = new MessageEventArgs(); ,您在内存中只有一个存储数据的位置。

这意味着当您更改成员时在那种情况下,这种变化也会反映在事件方法中。

睡眠工作的原因是因为你减慢了进程的速度,以便在数据网格改变之前将值打印在数据网格中在循环中。



您应该在 MessageEventArgs 类中添加一个或多个带参数的构造函数。

例如:

  public  MessageEventArgs( int  weekNo, int  dayNo)
{
this .WeekNo = weekNo;
.DayNo = dayNo;
}





这样就很容易为每条消息创建一个新的事件参数实例。

  public   void  LongOperationMethod(BindingList< status> _statusData) 
{
if (OnMessageSending!= null
{
/// 周的循环
for int k = 0 ; k < 2 ; ++ k)
{
/// 每天循环
int i = 0 ; i < 7 ; ++ i)
{
/// 计算每日
for int j = 0 ; j < 1000 ; ++ j)
{
// 要做
}

OnMessageSending( new MessageEventArgs(k,i));

}
}
OnMessageSending( new MessageEventArgs( 流程成功完成......));
}
其他
{
throw new ArgumentException( 事件没有上升,所以我们不能继续工作。);
}
}
< / 状态 &GT; 跨度>


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:

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:

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));
     
    }
}

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:
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.

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>