sam
sam

Reputation: 2616

interface freeze when trying to update datagridview

I am using below code to copy files and set status column at datagridview to inform user that connection is up but when I press the button to execute is method the interface freeze...

I have searched a lot i know that using task.run(); is not possible since it is not included in .not 4 it a new feature of .net 4.5 i also know that Task.Factory.StartNew(); can be used instead of using task.run() but it has a lot of risk as implicit thread and i know that using explicit threading is also good choose a

i want a little help to go on with my project and keep learning instead of getting stacked at that boring point

public void PatchUpdates()
{
    try
    {
        foreach (DataGridViewRow OfficeListRow in DGV_OfficeList.Rows)
        {
            string OfficeIPAddress = OfficeListRow.Cells[3].Value.ToString();

            foreach (DataGridViewRow FileListRow in DGV_FileList.Rows)
            {
                string SoruceFileNamePath = FileListRow.Cells[4].Value.ToString();

                string DestinationFileNamePath = @"\\" + OfficeIPAddress + @"\usb1_1\test\" + Path.GetFileName(SoruceFileNamePath);

                //check if connection to remote server is available
                var vResult = CheckOffice(OfficeIPAddress);

                if (vResult == 1)
                {
                    DGV_OfficeList[4, DGV_OfficeList.CurrentCell.RowIndex].Value = "Connected";
                    File.Copy(SoruceFileNamePath, DestinationFileNamePath, true); //copy files...
                }
                else if (vResult == 0)
                {
                    DGV_OfficeList[4, DGV_OfficeList.CurrentCell.RowIndex].Value = "disconnected";
                    break;
                }
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

check office code below

    public int CheckOffice(string _ipAddress)
    {
        int timeout = 120;
        string data = "PingTestData";
        byte[] buffer = Encoding.ASCII.GetBytes(data);

        Ping PingSender = new Ping();
        PingOptions options = new PingOptions();

        options.DontFragment = true;

        PingReply reply = PingSender.Send(_ipAddress, timeout, buffer, options);

        if (reply.Status == IPStatus.Success)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

below how I have tried to make thread but that wont solve my problem

public void PatchUpdates()
{
    try
    {
        foreach (DataGridViewRow OfficeListRow in DGV_OfficeList.Rows)
        {
            string OfficeIPAddress = OfficeListRow.Cells[2].Value.ToString();

            foreach (DataGridViewRow FileListRow in DGV_FileList.Rows)
            {
                string SoruceFileNamePath = FileListRow.Cells[4].Value.ToString();
                string DestinationFileNamePath = @"\\" + OfficeIPAddress + @"\usb1_1\test\" + Path.GetFileName(SoruceFileNamePath);


                Thread foregroundthread = new Thread(() => CheckOffice(OfficeIPAddress));

                foregroundthread.Start();

                //check if connection to remote server is available
                if (CheckOffice(OfficeIPAddress) == 1)
                {
                    DGV_OfficeList[3, DGV_OfficeList.CurrentCell.RowIndex].Value = "Connected";
                    //file.copy(sorucefilenamepath, destinationfilenamepath, true); //copy files...
                }
                else if (CheckOffice(OfficeIPAddress) == 0)
                {
                    DGV_OfficeList[3, DGV_OfficeList.CurrentCell.RowIndex].Value = "disconnected";
                    break;
                }
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

I have also tried this but as i said thask.run is not available at dot net 4

    var task = Task.Run(() =>
        {
            var result = CheckOffice(OfficeIPAddress);

            this.BeginInvoke((Action)(() =>
            {
                if (result == 1)
                {
                    DGV_OfficeList[4, DGV_OfficeList.CurrentCell.RowIndex].Value = "Connected";
                    //file.copy(sorucefilenamepath, destinationfilenamepath, true); //copy files...
                }
                else if (result == 0)
                {
                    DGV_OfficeList[4, DGV_OfficeList.CurrentCell.RowIndex].Value = "disconnected";
                }
            }));
        }
    );

--------------------------------------------------------- Update ---------------------------------------------------------

    public void PatchUpdates()
    {
        try
        {
            foreach (DataGridViewRow OfficeListRow in DGV_OfficeList.Rows)
            {
                string OfficeIPAddress = OfficeListRow.Cells[3].Value.ToString();
                int RowNum = OfficeListRow.Index;

                foreach (DataGridViewRow FileListRow in DGV_FileList.Rows)
                {
                    string SoruceFileNamePath = FileListRow.Cells[4].Value.ToString();
                    //string DestinationFileNamePath = @"\\" + OfficeIPAddress + @"\usb1_1\test\" + Path.GetFileName(SoruceFileNamePath);
                    string DestinationFileNamePath = @"F:\test\" + Path.GetFileName(SoruceFileNamePath); //TestPurpose

                    Thread t2 = new Thread(new ThreadStart(() =>
                    {
                        int vResult = CheckOffice(OfficeIPAddress);
                        UpdateUI(vResult, RowNum, SoruceFileNamePath, DestinationFileNamePath, OfficeIPAddress);
                    }));
                    t2.Start();
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

The UpdateUI method to update UI...

    public void UpdateUI(int vResult, int RowNum, string SoruceFileNamePath, string DestinationFileNamePath,string OfficeIPAddress)
    {
        try
        {
            var timeNow = DateTime.Now;

            if ((DateTime.Now - PreviousTime).Milliseconds <= 10)
                return;

            SynchronizationContext.Post(new SendOrPostCallback(o =>
                    {
                        if (vResult == 1)
                        {
                            DGV_OfficeList[4, RowNum].Value = "Connected";
                            //File.Copy(SoruceFileNamePath, DestinationFileNamePath, true);
                            //MessageBox.Show("Pingable " + OfficeIPAddress); //TestPurpose
                        }
                        else if (vResult == 0)
                        {
                            DGV_OfficeList[4, RowNum].Value = "Disconnected";
                            //MessageBox.Show("Not reachable"); //TestPurpose
                        }
                    }), vResult);

            PreviousTime = timeNow;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

enter image description here

enter image description here

Upvotes: 0

Views: 2211

Answers (1)

CodingYoshi
CodingYoshi

Reputation: 27039

Short answer: There is nothing wrong with your code. The design is not good.

Long Answer

Have you heard someone say "I can only do one thing at a time!" Well that is what is going on here. Your windows forms application's code is being executed by 1 thread and that thread can only do one thing at a time. When it is pinging, it waits for the reply. If the reply is successful it then copies a file. Since you have a loop it keeps doing this until it has completed all rows.

While it is doing that, you are probably clicking other things in the UI, but your thread "can only do 1 thing at a time". It is busy doing the stuff in the loop. Therefore, when you are clicking, you just have to wait.

So how do I fix it so the UI does not freeze?

In plain English, you need to do this. Imagine you are a thread:

I am the UI thread and my ultimate goal is to keep the UI responsive. I do not want the UI to freeze. Therefore, if I need to do anything except UI work, I am going to ask someone else to do it. While that someone else is doing other work, I will be free to do UI work.

That someone else is another thread. Here is an example, read my comments in the code and apply it to your application. If you want to run this code, create a form Form1 with a label named label1 and two buttons. Assign ButtonClickHandlerAsync to one button's click handler and Stop_Click to the other button.

The whole action starts when you click the button which executes ButtonClickHandlerAsync. While it is doing work, you can click the other button and it will show a message box and stay responsive. By the way I have copied this code from here but I added my own comments inside the code so you know what is going on.

public partial class Form1 : Form {

   // We need this because this will allow us to interact with UI controls. UI controls can only be accessed by the thread that 
   // created the UI control. In this case it is the thread which started the application so the main thread.
   private readonly SynchronizationContext synchronizationContext;
   private DateTime previousTime = DateTime.Now;

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

   private void Stop_Click(object sender, EventArgs e) {

      // I am the UI thread. I can do this because T2 is helping me do the loop.
      MessageBox.Show( "I am doing other things." );
   }

   private async void ButtonClickHandlerAsync(object sender, EventArgs e) {
      button1.Enabled = false;
      var count = 0;

      // I am the UI thread. I have other things to do. So please run this loop by using a thread from the thread pool.
      // When you are done running the loop let me know (This is what the await does)
      // I am the UI thread so I am going to return back from right here
      // to the point where ButtonClickHandlerAsync was called from. (it was called by a click, so when it returns it will have nothing
      // to do. Therefore, it will be ready to react to another UI job such as another click or update the UI etc.
      await Task.Run( () =>
      {
         // I am a thread from the thread pool. My name is T2. I am helping the UI thread so the UI thread can do other things.
         for( var i = 0; i <= 5000000; i++ ) {
            UpdateUI( i );
            count = i;
         }
      } );


      // I am the UI thread. Ok looks like the loop is done. So I will do the following 2 lines of work
      label1.Text = @"Counter " + count;
      button1.Enabled = true;
   }

   public void UpdateUI(int value) {

      // I am T2. I am helping the UI thread.
      var timeNow = DateTime.Now;

      if( ( DateTime.Now - previousTime ).Milliseconds <= 50 )
         return;

      // I do not have access to the UI controls since I did not create them. So I am just going to ask the synchronizationContext
      // to do this for me by giving it a SendOrPostCallback
      synchronizationContext.Post( new SendOrPostCallback( o =>
      {
         // I am the UI thread. I will do this.
         label1.Text = @"Counter " + ( int ) o;
      } ), value );

      // I am T2. I will do this and then return and do more work.
      previousTime = timeNow;
   }

How can you fix your code?

You can do CheckOffice and copying the files using a thread from the threadpool. That thread can use the synchronizationContext to if it needs to interact with the UI. The main UI thread can stay free to do other things while the thread from the thread pool is checking the office and copying a file which could take a long time, especially, if the file is big.

"I am the UI thread. I have no time to wait for a ping reply." "I am the UI thread. I have no time to copy a file from one location to another location which can take seconds or minutes. My job is to keep the UI responsive."

EDIT

I wrote the above answer before the OP wrote the restriction of .NET 4. But I am pretty sure they have created a NuGet package for this. See here and here.

If you cannot use async and await, the same concepts above apply to threading.

Thread t2 = new Thread( new ThreadStart(() =>
{
   for( var i = 0; i <= 5000000; i++ ) {
      UpdateUI( i );
      count = i;
   }
} ) );
t2.Start();

Upvotes: 4

Related Questions