-2

I have a program made using WinForms in C# that is using API provided by another developer and it has to be run in the main thread (because it is using WM_Messages, but there might be other reasons as well) - so I can't use BackgroundWorker. My program runs for at least 15 minutes with that API. So when I run it and click on the form, it will freeze and crash, because it is not responding. What can I do to make the form responsive and not trigger the Windows alert "The application is not responding" while it is using that API?

Here is the code that I am running in a loop for all fileNames from given folder:

fApi.ApiSetDates(DateTime.MinValue, DateTime.MinValue, invoiceIssueDate.Year, invoiceIssueDate.Month);
try
    {
        if (fApi.ImportFakturFromXML(fileName) != 0)
        {
            throw new Exception(fApi.GetLastError());
        }
        File.Delete(fileName);
    }
    catch (Exception x)
    {
        MessageBox.Show(x.ToString());
    }
TK-421
  • 294
  • 1
  • 7
  • 26
  • 1
    Post code that shows what you're actually doing, what kind of functions you're calling and a description of what these functions are used for. – Jimi Dec 15 '21 at 14:19
  • @Jimi I have added code but I don't know how is it relevant to my question – TK-421 Dec 15 '21 at 14:26
  • 1
    There's not much background info here, but note that the API function will still be able to post or send message to your main window (I assume you pass it the handle at some point) even if you call it from a background thread. – 500 - Internal Server Error Dec 15 '21 at 14:28
  • @Jimi I am asking about a general thing - how to make WinForm main thread respond without the use of BackgroundWorker. Whatever code is being run in the main thread doesn't really matter, right? – TK-421 Dec 15 '21 at 14:29
  • @500-InternalServerError As I have stated in my question - the API uses WM_Messages, so it can't be run in the BackgroundWorker, not because it has to communicate with the UI in the main thread. – TK-421 Dec 15 '21 at 14:30
  • Because nobody knows what these API calls are doing, so it's hard to tell if you can run these calls in a Threadpool Thread and return results to the UI Thread, using, e.g., an `IProgress` delegate -- There are no WM_Something calls - that may need to use Handles - in the code you posted anyway, so I'm not sure what the issue is. – Jimi Dec 15 '21 at 14:31
  • @Jimi there are no WM_Messages in my code, because the API is using them, not my program. And I am not asking about running my code in a different thread, I am asking about keeping form responsive to Windows while code runs in main thread. – TK-421 Dec 15 '21 at 14:32
  • 1
    Are you saying a single call into the other developer's code can take 15min? – adv12 Dec 15 '21 at 14:33
  • The third part of my initial comment is *a description of what these functions are used for*. Does an API function send messages to the UI directly (to a Form or a Controls)? It would be weird. You're not passing any handle in those calls. – Jimi Dec 15 '21 at 14:34
  • @adv12 no, I am saying "Here is the code that I am running in a loop for all fileNames from given folder:" - one call takes 30 seconds to a minute, and I do it for 30-60 files. – TK-421 Dec 15 '21 at 14:35
  • 1
    @TK-421 in that case, you have the opportunity to yield from your own code. I would recommend setting up a Timer with Interval 0 and running one iteration on each Tick. That would at least allow you to refresh your UI every 30 seconds to a minute. – adv12 Dec 15 '21 at 14:36
  • @Jimi the API communicates with another software for which it was made and updated the database of that software. It doesn't send anything to my program, I only display a message if it has failed, as it can be seen in the code. – TK-421 Dec 15 '21 at 14:37
  • So, you don't really care to make those calls in the UI Thread, you just need to marshal the results to the UI Thread. You can most probably just Task.Run those calls -- It's not clear when or how frequently those calls are made. – Jimi Dec 15 '21 at 14:39
  • @adv12 If you could write an answer and explain how to set up a Timer and what to do for each tick (I mean what code would refresh UI each tick) that would be exactly the answer that I need. – TK-421 Dec 15 '21 at 14:40
  • 1
    One basic way to hide the problem is to hide the window, use NotifyIcon to alert the user when the job is done. Another way is to create a worker thread that is friendly to such a library, [example](https://stackoverflow.com/a/21684059/17034). – Hans Passant Dec 15 '21 at 15:22

1 Answers1

1

Here is the non-designer-generated code for a small Windows Form that performs many long-running, UI thread-blocking actions in a loop when its lone button is clicked. This is more readable and maintainable code than what you'd get with the Timer I initially suggested in the comments, and it's slightly more straightforward than what D.Kastier suggests in the comments on his answer.

namespace LongRunningLoop
{
   public partial class Form1 : Form
   {
      private bool m_Closed = false;

      public Form1()
      {
         InitializeComponent(); // set up button1
      }

      // async void event handler.  Usually async void
      // is a no-no, but here we really do want to fire and forget.
      // We prevent nasty things from happening by disabling the UI
      // before starting the main work and exiting if we detect
      // that the form has been closed.
      private async void button1_Click(object sender, EventArgs e)
      {
         // Disable the UI so there's no reentrancy during the Task.Delay()
         button1.Enabled = false;
         for (int i = 0; i < 60; i++)
         {
            if (m_Closed)
            {
               // Don't keep doing work if the user has closed the form
               break;
            }
            Thread.Sleep(5000); // A long-running, blocking call (sleep thread for 5 seconds)
            await Task.Delay(100); // Yield to allow other events to be processed
         }
         // Re-enable the UI
         button1.Enabled = true;
      }

      private void Form1_FormClosed(object sender, FormClosedEventArgs e)
      {
         // Set a flag to signal the loop to exit
         m_Closed = true;
      }
   }
}
adv12
  • 8,443
  • 2
  • 24
  • 48
  • So "await Task.Delay(100);" is what is making the UI responding and not closed by Windows as a "The application is not responding"? – TK-421 Dec 16 '21 at 11:13
  • 1
    Essentially, yes. It returns control to the UI thread, saying “pick up where I left off after 100ms.” The UI thread is always processing messages, so the 100 ms gives it time to process whatever input or other messages it received while it was tied up in your code. It’s when Windows notices your app hasn’t been processing those messages that it shows the application not responding message. It remains to be seen whether the Task.Delay will happen often enough to satisfy Windows that your app is alive. – adv12 Dec 16 '21 at 12:48
  • 1
    My last comment was a bit misleading technically. All the code you see is executing on the UI thread; by “returns control to the UI thread” I really mean “returns control to the message-processing loop” or something like that, but even that is confusing because it never left that loop; handling your event is one of the things the loop is responsible for. Maybe I mean “allows the message-processing loop to continue to other messages.” Not that you need to know any of that for the code to work. – adv12 Dec 16 '21 at 13:02