Home > Net >  Timer OnTimedEvent causing UI to become unresponsive but should be running on another thread
Timer OnTimedEvent causing UI to become unresponsive but should be running on another thread

Time:10-29

I have a timer, that triggers a timed event that iterates through a TreeView and pings all the IP addresses associated with each node.

I want this to be done in the background on a separate thread. I (probably wrongly) thought that an OnTimedEvent exists on a separate thread to the UI.

However, when my program starts, it is responsive. Then as soon as the timed event triggers the UI becomes unresponsive, doesn't crash but doesn't regain responsiveness until the program is stopped in the debugger and ran again.

I've been messing around with async and await in order to get the required behaviour however I am unable to achieve this.

Here's how I initialise the Timer currently.

public Form1()
{
   InitializeComponent();
   StartTimer();
}

private async void StartTimer()
{
   aTimer = new System.Timers.Timer(15000);
   await Task.Run(() => aTimer.Elapsed  = OnTimedEvent);
   aTimer.AutoReset = true;
   aTimer.Enabled = true;
   aTimer.SynchronizingObject = this;
}

And the OnEventTimed method

private async void OnTimedEvent(Object source, ElapsedEventArgs e)
{
   // Iterate through all root nodes
   foreach (TreeNode tn in mainTree.Nodes)
   {
      TreeNodeCollection rootNodes = ((TreeNode)tn).Nodes;

      foreach (TreeNode node in rootNodes)
      {
         // Create ping object
         System.Net.NetworkInformation.Ping pinger = new();
         PingReply pingReply;

         if (node.Tag != null)
         {
             pingReply = pinger.Send(node.Tag.ToString());

             if (pingReply.Status.ToString().Contains("Success"))
             {
                UpdateUI(node, 1); // If successful, set the image index to show green 
             }
             else if (pingReply.Status.ToString().Contains("Failed"))
             {
                UpdateUI(node, 2);
             }
          }
       }


       // Iterate through all the children of the 'root' nodes
       foreach (TreeNode child in tn.Nodes)
       {
           // Extract all nodes from these children
           TreeNodeCollection myNodes = ((TreeNode)child).Nodes;

           // Create ping object
           System.Net.NetworkInformation.Ping pinger = new();
           PingReply pingReply;

           // Iterate through each of the nodes, send a ping request and then update the UI based on the result of the ping
           foreach (TreeNode node in myNodes)
           {
              if (node.Tag != null)
              {
                  pingReply = pinger.Send(node.Tag.ToString());

                  if (pingReply.Status.ToString().Contains("Success"))
                  {
                     UpdateUI(node, 1); // If successful, set the image index to show green 
                  }
                  else if (pingReply.Status.ToString().Contains("Failed"))
                  {
                     UpdateUI(node, 2);
                  }
               }
           }
       }

   }
}

I am trying to use await Task.Run(() => aTimer.Elapsed = OnTimedEvent); but it's not giving me the behaviour I expect any help would be appreciated

CodePudding user response:

My suggestion is to ditch the mischievous System.Timers.Timer, and use instead a normal System.Windows.Forms.Timer. Then in the Tick event handler just offload to the ThreadPool any work that you don't want to happen on the UI thread. All interaction with UI controls should stay on the UI thread. Here is an (untested) example:

private async void timer_Tick(object sender, EventArgs e)
{
    timer.Enabled = false;
    //...

    if (node.Tag != null)
    {
        string ip = node.Tag.ToString();
        PingReply pingReply = await Task.Run(() => pinger.Send(ip));
        //...

    timer.Enabled = true;
}

The Task.Run is the method that does the offloading. The action of the Task.Run is invoked on the ThreadPool. The result of the invocation is marshaled back on the UI thread, by awaiting the resulting Task<PingReply>.

Notice how the Task.Run delegate is not touching at the slightest the TreeNode UI elements. It gets an argument of type string, and returns a result of PingReply. All the UI-related work is performed exclusively on the UI thread.

  • Related