0

I am currently working on a modular application using multiple dynamically loaded plugins. The main window also works as an output for feedback messages from the various plugins. This is a central requirement so the plugins can't have their own output.

The main program consists of 3 classes:

  • Main class that creates the GUI and handles the plugin calls

  • Second class that collects the plugins from a specified folder using MEF

  • Export class that can be accessed from a Plugin to send a message (string[]) to show in the main window

The Export class uses an event for receiving messages, to which the main class subscribes and writes the message into a DataGridView.

This works fine as long as I start a plugin without putting it into a separate thread. But as expected the mainform is frozen while the plugin is working. Now I tried to create separate threads for the plugins so I can have multiple ones running in the background simultaneously. Unfortunately this renders the message receiving a cross-thread operation.

With the use of an event I intended to make it thread-safe because the mainform subscribes to the Event and handles the messages in the "main-thread". Obviously I was wrong… I still don't quite get how the event-triggered method in the main window suddenly switches into the separate task...

Some additional Information:

  • Invoking is not an option because the output window consists of a lot more controls than just the DataGridView and these controls are constantly being modified whenever a message is received.

  • The mainform has a public static string[] for transferring the message's content from the export class to the main class

Is there any way to put the message writing method into the main thread but still able to subscribe to the event that is fired from another thread? Or maybe is there another approach to the task?

[Export]
    public class Exportklasse : Interfaces.IMain
    {
        public static event EventHandler MeldungEintragen;
        // Diese Methode aufrufen, um von Modulen aus Textmeldungen einzutragen
        public void MeldungEmpfangen(string[] MeldungInput, EventArgs e)
        {
            EventHandler EintragenEvent = MeldungEintragen;
            if (EintragenEvent != null) {
                MainForm.MeldungText = MeldungInput;
                EintragenEvent(this, e);
            }
        }
    }
  • Try watch this: https://learn.microsoft.com/it-it/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls – Emanuele Nov 19 '19 at 08:40
  • 1
    Because eventhandler not called by Main class, but by the plugin. Usually event handlers called by the object which raises an event, which in your case is plugin and if plugin running on another thread UI logic get called from another thread as well – Fabio Nov 19 '19 at 08:47
  • 1
    _"Invoking is not an option because the output window consists of a lot more controls than just the DataGridView and these controls are constantly being modified whenever a message is received."_ Can you elaborate on that? As far as I can read out of the question you have about 3 options: 1. Control.Invoke, 2. Switch to async/await , 3. Switch to BackgroundWorker , - So, I really do think Invoke would be the least invasive. – Fildor Nov 19 '19 at 08:53
  • The output consists of a DataGridView and multiple checkboxes to filter the messages. These checkboxes are created dynamically whenever a message is being received and can be switched to invisible when there is no valid message left for this filter. So I have several dozens of Methods to modify These filter checkboxes. I would have to invoke every single command to any of These checkboxes.. Can't really believe this is the best Option to solve this task.. Unfortunately I'm still a beginner in programming and the requirements for the whole Application often change by orders "from above" – Raketenmaulwurf Nov 19 '19 at 09:34
  • I am currently investigating the possibility to put the event into the main class and raise it from the Export class. according to this: https://stackoverflow.com/questions/4378339/raise-an-event-of-a-class-from-a-different-class-in-c-sharp it should be possible but it requires a new object of main class for every Plugin which as far as i know doesn't work with WinForms. I'm trying to adapt this solution to my needs – Raketenmaulwurf Nov 19 '19 at 09:39
  • _"and the requirements for the whole Application often change by orders "from above""_ That's the nature of our job. You need to embrace it. Easily said but hard to do, I know. Anyway. Without seeing your code, I _guess_ one single Invoke should suffice. Namely the Message event handler should contain a check if it is being executed on the GUI Thread and Invoke _itself_ if not. From then on anything it does will be executed on the UI Thread. – Fildor Nov 19 '19 at 09:58
  • Added my export class into the question. Where do I have to put the invoke now? This goes far beyond to what i have programmed before – Raketenmaulwurf Nov 19 '19 at 10:12
  • Sounds to me like "I have several dozens of methods to modify the visibility of the checkboxes" is where this goes wrong. Have a single method that examines the list of filterable events and shows the checkboxes accordingly. Checkboxes can be dynamic; just have the event grouping id be the key in a dictionary and the checkbox the value, so you can easily map. Setting visibility is as simple as making all checkboxes invisible then iterating the list of events, looking at each event's group (or however you map an event to a checkbox) and looking up the cb from the dictionary – Caius Jard Nov 19 '19 at 11:45
  • The amount of methods that Control the checkboxes directly is just 3. One to hide table-entries that don't match the current filters, another to hide filters that don't match the remaining table entries (sounds weird but every column has it's own set of filters so yes this actually happens) and the last one that sorts all remaining filters alphabetically again... – Raketenmaulwurf Nov 19 '19 at 12:07
  • But they are being called depending on other controls and button events so technically These dependancies control the boxes although not directly – Raketenmaulwurf Nov 19 '19 at 12:11

1 Answers1

1

Would something like this work?:

private readonly IBackendWorker worker;
private SynchronizationContext uiContext;
public MainForm(IBackendWorker worker)
{
    InitializeComponent();
    this.worker = worker;
    uiContext = SynchronizationContext.Current;
    worker.BackendEvent += OnWorkerBackEvent;
   ...
}

and the eventhandler:

private void OnWorkerBackEvent(int count)
{
    // schedule the action on the UI synchronisation context.
    uiContext.Post((c) =>
    {
       // put your code to interact with UI here
       outputTextBox.Text = count.ToString();
    }, null);
    // or: uiContext.Post((c) => outputTextBox.Text = c.ToString(), count);
}

The eventhandler is executed in whatever thread the event is triggered from, but executes the action in the UI-thread.

Johan Donne
  • 3,104
  • 14
  • 21