Use SafeDispatcher for custom hosted controls in Unified Service Desk

Unified Service Desk is a Windows Presentation Foundation (WPF)-based application where all the operations in Unified Service Desk are executed on the main WPF Dispatcher thread. The WPF Dispatcher class provides services for managing the queue of work items for a thread.

You can extend Unified Service Desk by creating custom controls and hosting it within Unified Service Desk. However, if a custom hosted control contains faulty code or executes operations using new threads without appropriately handling exceptions during code execution, it may cause stability issues in Unified Service Desk, and might even cause the client application to freeze or become unresponsive. The unhandled exceptions in third-party custom controls makes it challenging to identify, troubleshoot, and resolve the issue by the product/support team as they might not have access to the information why an error/exception occurred in Unified Service Desk, and the exact code that caused the error.

Introducing SafeDispatcher that provides a powerful and informative exception handling mechanism for custom hosted controls in Unified Service Desk by providing out-of-box logging for unhandled exceptions with detailed information about the source and cause of the exception, and allowing you to configure or overwrite the SafeDispatcher exception handling to perform some other steps. This also prevents the Unified Service Desk client from becoming unresponsive because of unhandled exceptions in the custom hosted control code.

What is SafeDispatcher?

SafeDispatcher is built on the same lines as the WPF Dispatcher, and provides resilient and informative exception handling for custom hosted controls within Unified Service Desk. It is exposed as a protected property, SafeDispatcher, on the DynamicsBaseHostedControl class, which makes SafeDispatcher automatically available for all Unified Service Desk custom hosted controls that are derived from the DynamicsBaseHostedControl class.

Note

Do not use the SafeDispatcher class in your code to work with SafeDispatcher. Instead, you must use the SafeDispatcher property on your custom hosted control instance that is derived from the DynamicsBaseHostedControl class to use SafeDispatcher.

Just like WPF Dispatcher, SafeDispatcher provides methods such as BeginInvoke, Invoke, and InvokeAsync to run operations synchronously or asynchronously on SafeDispatcher with an additional boolean parameter, runOnMainUiThread, that controls whether to run SafeDispatcher on UI thread or not.

SafeDispatcher provides the following benefits:

  • Protected UI Dispatcher thread: Developers can run all UI-dependent operations on SafeDispatcher by setting the runOnMainUiThread parameter to "true" in the invoke method to run the SafeDispatcher on UI thread. Any unhandled exception raised on main UI dispatcher will be handled safely at the hosted control level instead of bubbling up to the global DispatcherUnhandledException Event handler.

  • Protected non-UI Dispatcher thread: Developers can run all UI-independent code on SafeDispatcher. by setting the runOnMainUiThread parameter to "false" in the invoke method to run the SafeDispatcher on non-UI thread. Any unhandled exception raised on main non-UI dispatcher will be handled safely at the hosted control level instead of bubbling up to the global DispatcherUnhandledException Event handler.

  • Detailed information about exception source and cause:: The SafeDispatcher exception handler is raised when an unhandled exception is raised at the DynamicsBaseHostedControl level by the UI or non-UI thread, which allows Unified Service Desk to capture critical information at the hosted control level such as hosted control name, hosted control type, method name, and complete stack trace to identify the exact location and cause of the exception.

  • Configure or override SafeDispatcher exception handler: Developers can leverage the out-of-box behavior of the SafeDispatcher exception handler to prompt the user with information about the unhandled exception or override the behavior as per their business requirement such as configure additional logging, close session based controls, or exit the Unified Service Desk client.

How to use SafeDispatcher?

The SafeDispatcher property is available for all Unified Service Desk custom hosted control instances that are derived from the DynamicsBaseHostedControl class. A SafeDispatcher instance will be available to run on UI thread when the custom hosted control is initialized. However, a SafeDispatcher instance will only be available to run on non-UI thread when you execute the invoke method for the first time.

  • Synchronously invoke a UI-specific function using the SafeDispatcher

    SafeDispatcher.Invoke(() =>
                {
                    ProcessData();
                }, DispatcherPriority.Normal, CancellationToken.None, true);
    

    OR

    SafeDispatcher.Invoke(() =>
                {
                    ProcessData();
                }, DispatcherPriority.Normal, CancellationToken.None);
    

    Note

    For UI-specific function, you should set the runOnMainUiThread optional parameter to "true". If you do not specify a value for this parameter, by default "true" is passed. So, any of the above method definition works fine.

  • Asynchronously invoke a UI-specific function using SafeDispatcher. You can use either the BeginInvoke or InvokeAsync method.

    SafeDispatcher.BeginInvoke(new Action(() =>
                {
                   ProcessData();
                }));
    
    

    OR

    SafeDispatcher.InvokeAsync(new Action(() =>
                {
                   ProcessData();
                }));
    
    

Customize the SafeDispatcher exception handler

With the introduction of SafeDispatcher, all unhandled exceptions in Unified Service Desk will raise the SafeDispatcherUnhandledException Event instead of the global DispatcherUnhandledException Event. The SafeDispatcherUnhandledExceptionHandler Method provides an out-of-box exception handler for SafeDispatcher to display information to the Unified Service Desk user with the following details: source control where the exception occurred and detailed info about the exception.

You can also override the out-of-box exception handling for SafeDispatcher to perform other operation such as prompt the user to close a session-based non-dynamic hosted control.

The following sample code demonstrates how you can override the out-of-box SafeDispatcher exception handler to display a message box to prompt the user to close the custom hosted control when an exception occurs:

protected override void SafeDispatcherUnhandledExceptionHandler(object sender, SafeDispatcherUnhandledExceptionEventArgs ex)
{
    string error = String.Format(CultureInfo.InvariantCulture,
        "Error in hosted control  Application:{0} - Exception : {1} \r\nInnerException\r\n {2}", this.ApplicationName, ex.Exception, ex.InnerException);
    DynamicsLogger.Logger.Log(error, TraceEventType.Error);
    if (MessageBox.Show("Exception occurred in hosted control - " + this.ApplicationName + ".Do you wish to close it ?", "Question", MessageBoxButton.YesNo,
        MessageBoxImage.Warning) == MessageBoxResult.Yes)
    {
        SafeDispatcher.BeginInvoke(() => { this.desktopAccess.CloseDynamicApplication(this.ApplicationName); });
    }
}

Migrating from WPF Dispatcher to SafeDispatcher in existing custom hosted controls

As the contract between WPF Dispatcher and SafeDispatcher is almost identical, the effort to move from WPF Dispatcher to SafeDispatcher is minimal. To migrate any hosted control instance derived from the DynamicsBaseHostedControl class, replace all "Dispatcher" instances with "SafeDispatcher".

For example, consider the following code:

Dispatcher.Invoke((System.Action)delegate()
{
    DynamicsLogger.Logger.Log("Raising SetupHotKey's", TraceEventType.Verbose);
    SetupHotkeys();
    CRMGlobalManager.AppWithFocusChanged += CRMGlobalManager_AppWithFocusChanged;
    FireEvent("DesktopReady");
    InitializeFocusSelection();
});

Replace Dispatcher in the above code with SafeDispatcher; rest of the code remains the same:

SafeDispatcher.Invoke((System.Action)delegate()
{
    DynamicsLogger.Logger.Log("Raising SetupHotKey's", TraceEventType.Verbose);
    SetupHotkeys();
    CRMGlobalManager.AppWithFocusChanged += CRMGlobalManager_AppWithFocusChanged;
    FireEvent("DesktopReady");
    InitializeFocusSelection();
});

Things to consider when using SafeDispatcher

SafeDispatcher offers a multithreaded model that is highly beneficial in dispatching functions synchronously or asynchronously to UI or non-UI threads. Operations that need to be run on threads that are available with fault tolerance should be executed on SafeDispatcher. However multi-threading should be implemented very carefully to avoid deadlock between threads. One such example is dispatching from non-UI thread to main WPF Dispatcher synchronously. Lets consider this example:

Thread thread = new Thread(() =>
{
    Dispatcher.Invoke(ProcessData);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Priority = ThreadPriority.Highest;
thread.IsBackground = true;
thread.Start();
thread.Join();

The thread.Join() method is causing the main thread to get blocked waiting for the termination of single-threaded apartment (STA) thread, but the STA thread is waiting for the main thread to finish the execution of ProcessData. This causes your app in a deadlock situation.

Similarly, consider the following example:

// Invoke on STA thread
SafeDispatcher.Invoke(() =>
{
    // Invoke back on main dispatcher
    SafeDispatcher.Invoke(() =>
    {
        ProcessData();
    });
}, false);

The SafeDispatcherUnhandledExceptionHandler method will be called if an exception happens on the WPF Dispatcher or on the STA non-UI thread and will be raised on the respective thread on which the exception happened. You should be careful in making sure you don’t place the above combination in this handler, that is, if the exception occurred on non-UI thread, do not dispatch synchronously to the main UI dispatcher.

protected override void SafeDispatcherUnhandledExceptionHandler(object sender, SafeDispatcherUnhandledExceptionEventArgs ex)
{
    Dispatcher.Invoke(LogException);            // Incorrect
    SafeDispatcher.Invoke(LogException);        // Incorrect
    SafeDispatcher.BeginInvoke(LogException);   // Correct
    SafeDispatcher.InvokeAsync(LogException);   // Correct
}

See also

Create custom Unified Service Desk hosted control Extend Unified Service Desk Configure client diagnostic logging in Unified Service Desk