-1

I'm struggling with a legacy library (written in Delphi I think, no access to the source code). It comes with a C# Windows Forms demo application, in which it:

  1. Performs some basic usage of the legacy library in the main thread (get the version etc)
  2. Performs any 'real' usage triggered by user input with the C# ThreadPool. Outputting the thread ids shows that all relevant scenarios work without issues.

However, when replicating this in a console application, it just does not work.

  • Executing relevant actions sequentially in the main thread -> ok
  • Mimicking the demo app with simple initial calls in the main thread and actions in worker threads -> usually not ok
  • Skipping main thread work and performing actions in worker threads -> usually only ok for the first thread doing work. Subsequent threads fail to perform the work correctly.

After a lot of head scratching I decided to create a super simple WPF app to see if that would make any difference. Well, it just works flawlessly ... I've put the redacted code at the bottom of my question.

Given that both the WPF app and the classical Windows Forms apps work without any issues, my main question is: what could cause this big difference regarding multithreaded libary usage between a Windows GUI app and a console app? To clarify: in my console app I tried many approaches, ThreadPool, Thread objects, Tasks, ... Nothing works reliably.

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            string version = "";
            Legacy.GetVersion(ref version);
            this.status.Text = version;
        }

        void output(string output)
        {
            this.status.Dispatcher.Invoke( () => { this.status.Text += output; });
            
        }

        void connect(string ip, uint id)
        {
            var settings = new Settings();
            settings.IPAddress = ip;

            output($"thread {Environment.CurrentManagedThreadId} is connecting to device {id}");

            var result = Legacy.Connect(id, settings);
            output($"connect result for device {id} and thread {Environment.CurrentManagedThreadId}: " + Legacy.ResultString(result));
            if (Legacy.Ok((uint)result))
            {
                var info = new Info();
                result = Legacy.GetInfo(id, info);
                output(info.DeviceId);
            }

            result = Legacy.Disconnect(id);
            output("disconnect result: " + Legacy.ResultString(result));
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ThreadPool.QueueUserWorkItem((object bla) => connect("10.0.0.146", 1));
            
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            ThreadPool.QueueUserWorkItem((object bla) => connect("10.0.0.139", 2));
        }
    }
}
Bob
  • 598
  • 3
  • 10
  • Do you have a `[STAThread]` attribute for your `Main()` method? If not, does anything change if you add it? – Matthew Watson Jul 04 '23 at 09:51
  • I did not, but testing shows no difference. – Bob Jul 04 '23 at 11:07
  • @MatthewWatson it's the right direction though, I notice that skipping GetVersion in the WPF app's main thread results in the same erroneous behaviour as what I observe in all cases in my console app. There seems to be something magical about a GUI main thread that correctly initializes the state of my legacy library. – Bob Jul 04 '23 at 11:57
  • 1
    Main thread might need to be `[STAThread]` but then you ALSO need to pump a message loop. See the various answers here: https://stackoverflow.com/questions/277771/how-to-run-a-winform-from-console-application – Matthew Watson Jul 04 '23 at 15:35
  • That's what I was starting to look into indeed, thanks for the pointer. – Bob Jul 05 '23 at 06:44

1 Answers1

1

Matthew pointed me in the right direction.

The final solution was, quick version:

static void Main(string[] args)
        {

            var messagePumpThread = new Thread(() => {
                string version = "";
                // very first usage of Legacy should be in this thread
                // to make sure it registers this thread as its message pump thread
                Legacy.GetVersion(ref version);
                Console.WriteLine($"thread {Environment.CurrentManagedThreadId} version {version}");
                Application.Run();

            });
            messagePumpThread.Start();

            // sync to make sure the message pump started correctly
            // make multi threaded use of Legacy library

        }
Bob
  • 598
  • 3
  • 10