interface freeze when trying to update datagridview

前端 未结 1 1888
暗喜
暗喜 2021-01-07 14:22

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

相关标签:
1条回答
  • 2021-01-07 14:49

    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();
    
    0 讨论(0)
提交回复
热议问题