Skip to content

Threads

Oddbjørn Bakke edited this page May 10, 2021 · 14 revisions

Listener thread

The reason why the application does not close after Main is "done" when running the plugin, is because events are picked up by a listener thread.
This is a foreground thread that will hold the application open, as long as the SDK is connected to TouchPortal.
The listener thread will then become the "new main" thread.

This is also the most common way of doing this.

_listenerThread = new Thread(ListenerThread) { IsBackground = false };
_listenerThread.Start();

private void ListenerThread()
{
    while (true)
    {
        try
        {
            var message = _streamReader.ReadLine()
                  ?? throw new IOException("Socket closed."); //Catch right underneath, and then the thread exits.

            //React on the message/event here:
            Console.WriteLine(message);
        }
        catch (IOException exception)
        {
            //Do some close logic.
            return;
        }
        catch (Exception exception)
        {
            //Ignore, log, etc.
        }
    }
}

SDK Close / Exceptions

The listener thread can be closes in this scenarios:

  • Socket closed (message returned is null), ex. TouchPortal Exits.
  • IOException is thrown.
  • Close message is sent. ex. Plugin was stopped from Settings.
  • Plugin closes. ex. Developer Calls the Close method.

When the listener thread is closed, it would normally take down the whole process, since no more foreground threads is running.
However, to be sure, it could be a good idea to call Environment.Exit(0); from your plugin OnClosedEvent Method.
This way the plugin will exit, even if there are other foreground threads running.

Plugin Methods Execution

All methods of the ITouchPortalEventHandler will be called from the listener thread.

  • By default the listener thread is locked until the call returns.

This might be changed in the future, if we run the plugin logic on a ThreadPool/Dispatcher/Execution thread instead.
At least it is room for improvements here.

  • Exceptions are swallowed by a global exception handler.

However, you might get some issued if using async void, that escapes this handler.
Wrap this then with a try/catch before calling anything, and handle it yourself.
This might be fixed with a custom SynchronizationContext, but that is probably not worth it.

See Parallelization for tips on how to get around this.

Why not async/await?

Async/await is a good approach in two scenarios responsiveness and scalability.
None of these fit well for the kind of task of this SDK, so for now we don't se to much reason to use the async implementation yet.

It might also introduce a lot of drawbacks like:

  • A listener thread must still be created, and then we a lot more threads in the process.
  • Adds complexity to the design.
  • More edge-cases can occur, think: Mono, dotnet, FullFramework, linux, mac, windows etc.
  • Harder for non-experienced developers.

Therefor we use ReadLine and not ReadLineAsync in this SDK today.
We are open to the idea to implement this using Async calls instead, however, this needs to be planned and executed well...
and right now, it's just not worth it.

However, you are free to use async await for parallelization of your actions.
Or come up with some ideas...
Or if you use another approach, please tell us about it. :)

Clone this wiki locally