Share via


Callback thread affinity

If your app is multithreaded, it must meet certain criteria to ensure correct operation.

Dragon Medical SpeechKit is an active component in your app. To keep your app responsive, it executes its code on its own dedicated threads. Interaction with other components in your app is restricted to specific threads. For example, text controls can only be accessed from the thread which created them. Every method call originating from SpeechKit towards your app is executed on a well-defined thread. This chapter explains the underlying model.

Integration guidelines

To ensure that your integration works properly, do the following:

  1. Call the Session's Open() method from the app's main UI thread only. For more information, see: Thread affinities of callbacks and incoming calls.

  2. Set focus to a VuiController from the app's main UI thread only. If the app has multiple UI threads, the main UI thread is the thread that owns the controls which are speech-enabled by that VuiController. For more information, see: Thread affinities of callbacks and incoming calls.

  3. Terminate UI threads only after all VuiControllers and the session have been closed. For more information, see: Lifetime of callback threads.

  4. Make sure your code is reentrant if you call Dragon Medical SpeechKit methods. For example, Dragon Medical SpeechKit should be able to manipulate the GUI before returning from a method call. For more information, see: Deadlock avoidance and reentrancy.

    Don't invoke methods synchronously between threads from which you call Dragon Medical SpeechKit methods. For more information, see: Deadlock.

Concepts

Components

The objects in your app which are not part of Dragon Medical SpeechKit are referred to as 'the integration'.

Method calls

The means of interaction between Dragon Medical SpeechKit and the integration is the invocation of a method, executed on a given thread. The set of possible method calls constitute the API of Dragon Medical SpeechKit. In addition to defined methods, there are also implicit ones. For example, when Dragon Medical SpeechKit modifies the text in an edit control, it calls methods on the .NET framework object representing that text control. This threading model also applies to implicit method calls.

A method call can be classified as either an 'incoming call' or a 'callback' (these also include events). The direction is defined from the point of view of Dragon Medical SpeechKit. Both incoming calls and callbacks are relevant for the threading model.

Threads

The threads involved in the model are .NET threads in the Windows process executing your app, created and owned either by Dragon Medical SpeechKit or the integration.

Threads owned by Dragon Medical SpeechKit are completely encapsulated; usually, the integration will neither receive callbacks on these threads, nor can it invoke incoming calls on them.

Threads owned by the integration are classified as 'UI threads' and 'worker threads'. A UI thread has a message queue associated with it, which enables the usage of operating system or .NET framework services to synchronously or asynchronously invoke methods on this thread. All other threads are worker threads.

Invocation

Code executing on a thread might need to invoke a method on a different thread. For example, invoking a method on an object which represents a UI element is only allowed on the thread which has created the object. The invocation can happen by any means: it's not necessarily carried out by the operating system, via its message queue mechanism. The invocation can be 'synchronous' or 'asynchronous', depending on whether the initiating code waits for its completion or not. Synchronous invocations are referred to as 'sending' a method (or method call, method invocation) to a thread, asynchronous invocations as 'posting'.

Only the invocation on UI threads is relevant for Dragon Medical SpeechKit.

Deadlock

If code on a source thread synchronously invokes a method on a target thread, the source thread will be blocked until that method is executed on the target thread. If the invoked method directly or indirectly tries to invoke a method on the source thread, it can't be executed, as the source thread is blocked, waiting for the first method call to return. The target thread is therefore also blocked, waiting for the execution of the second method call. This circular waiting situation is a deadlock; code that should subsequently run on the threads involved won't be executed; the app will probably stop working (Figure 1/a).

A deadlock can involve more than two threads (Figure 1/b). If thread 1 sends a method to thread 2, and thread 2 sends a method to thread 3, all three threads will deadlock, if thread 3 sends a method to either thread 2 or thread 1.

Figure 1: Deadlock situation involving two (a) and three (b) threads

Figure 1: Deadlock situation involving two (a) and three (b) threads.

Deadlock avoidance and reentrancy

In the first case outlined above, if two threads are waiting on each other, the deadlock can be avoided if the source thread stops waiting for the completion of the first method, executes the second method and then resumes waiting. In this case, the target thread can continue processing and complete the execution of the first method, which wakes up the source thread (see Figure 2). This applies to all situations when a method call would close a circle on the wait graph of the threads involved, resulting in a circular waiting situation.

Figure 2: Avoiding a deadlock by waking up a waiting thread

Figure 2: Avoiding a deadlock by waking up a waiting thread.

However, this deadlock avoidance mechanism implies that a thread, while waiting for a method call, can execute a seemingly unrelated method. This method call (Method2 in Figure 2) might change the state of the program, and when Send(Method1) returns and the execution of the source thread resumes, it might find a different state than before invoking Method1. Assumptions based on the single-threaded fully sequential execution of the code are not valid anymore. Code executing on the source thread is 'reentered'; code working correctly in this situation is called 'reentrant'.

In Figure 2 the Method2 call is a logical consequence of the execution of Method1; the author of the code executing on the source thread expects such a method being invoked while waiting for Method1. However, the deadlock avoidance mechanism can't differentiate based on causal relationships between method calls. A thread must be woken up every time a synchronous method invocation would close a circle on the wait graph, even if the last method call is not a consequence of the one the thread is waiting for. This means that the author of the code executing on the source thread can't assume anything about the methods possibly called back while waiting for an invocation (Figure 3).

Figure 3: Causally unrelated callback executed while waiting for a synchronous invocation

Figure 3: Causally unrelated callback executed while waiting for a synchronous invocation.

Thread affinities of callbacks and incoming calls

This section defines the callback threads Dragon Medical SpeechKit operates on and the thread affinities of method callbacks. Some incoming calls from your integration are restricted in terms of which thread they can originate from; this is also defined here.

Callback threads

A UI thread must be associated with each VuiController. Such a thread is referred to as a 'VuiController callback thread'. Dragon Medical SpeechKit uses this thread exclusively for operations and event notifications relevant for that VuiController. Session events are invoked on the VuiController callback thread of the most recently focused VuiController, unless your app has a designated session callback thread.

VuiController callback thread

The following callbacks are invoked on a VuiController's callback thread:

  • The events defined in the VuiController class.
  • All text control manipulation carried out directly by Dragon Medical SpeechKit for the text controls associated with this VuiController instance.

The VuiController's UI thread is its callback thread. Whenever a VuiController is focused, the callback thread is set to the thread from which focus was set. Don't set focus to a VuiController from a thread other than its UI thread.

Important

The COM threading model for threads running Windows Forms components must be set to single-threaded apartment. Not doing so can result in a deadlock.

Session callback thread

Session level callbacks are the events defined in the session class. These are always invoked on the session callback thread, which is by default the callback thread of the most recently focused VuiController. You can override this default behavior by calling the session's SetCallbackThread() method from a UI thread. Afterwards, this UI thread will be designated as the session callback thread. Each call of SetCallbackThread() overrides the previous setting.

If no VuiController has been activated and you haven't called SetCallbackThread(), the session callback thread will default to the thread from which the session was opened most recently.

Lifetime of callback threads

VuiController callback threads must exist as long as the VuiController is still open. The session callback thread must exist as long as the session is open. If the session callback thread wasn't set explicitly and thus defaults to the callback thread of the most recently focused VuiController, the lifetime requirements of the session callback thread will apply to that VuiController callback thread (as long as it plays the role of the session callback thread).

Thread affinity of incoming calls

Generally, a method call on Dragon Medical SpeechKit can originate from any worker or UI thread. Calls which implicitly designate the UI thread from which they're invoked as a callback thread, are exceptions. These are:

  • The method/property which effectively sets focus to a VuiController. For more information, see: Setting the VUI form focus.
  • SetCallbackThread() on the shared session instance.