Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Best way(s) to debug errors in separate threads?

Discussion in 'Scripting' started by FrankvHoof, Mar 15, 2023.

  1. FrankvHoof

    FrankvHoof

    Joined:
    Nov 3, 2014
    Posts:
    258
    I've recently taken over & upgraded the TikTokSharp-Library:
    https://github.com/frankvHoof93/TikTokLiveSharp

    This library allows users to connect to & receive events from a TikTok-LiveStream.

    The library runs fine in C#, but I'm getting some intermittent crashes in the Unity Editor.
    Since the stacktraces for these errors are empty, I'm having a hard time figuring out what the issue could be. All communication with the main thread is Dispatched to it (see code below), with every minimal potential conflicts.

    1. Is it okay to run Debug.Log from a separate thread? (I'm currently dispatching logs to the main thread as well, but this loses it's stack-trace, making it harder for users of the library to trace these logs back to where they were fired from).
    2. Are there any good ways that I can find out what's causing the crash? As stated previously; the library runs fine in C#-only, and there's barely any cross-communication with the main thread.


    Dispatcher-code:

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Threading;
    6. using UnityEngine;
    7.  
    8. namespace TikTokLiveUnity.Utils
    9. {
    10.     /// <summary>
    11.     /// Dispatches Actions & Coroutines between Threads
    12.     /// <para>
    13.     /// Script by Frank van Hoof
    14.     /// https://vanhoof.dev
    15.     /// </para>
    16.     /// </summary>
    17.     public class Dispatcher : MonoBehaviour
    18.     {
    19.         #region Properties
    20.         /// <summary>
    21.         /// Singleton-Instance
    22.         /// </summary>
    23.         private static Dispatcher _instance = null;
    24.         /// <summary>
    25.         /// Whether there is currently Work for the Main Thread
    26.         /// </summary>
    27.         private static volatile bool _hasWork = false;
    28.         /// <summary>
    29.         /// Backlog of Work for Simple Actions (No Parameters)
    30.         /// </summary>
    31.         private static readonly List<Action> _simpleActionBacklog = new List<Action>();
    32.         /// <summary>
    33.         /// Backlog of Work for Complex Actions (With Object Parameters)
    34.         /// </summary>
    35.         private static readonly Dictionary<Action<object[]>, object[]> _complexActionBacklog = new Dictionary<Action<object[]>, object[]>();
    36.         #endregion
    37.  
    38.         #region Methods
    39.         #region Async
    40.         /// <summary>
    41.         /// Runs Action on Thread
    42.         /// </summary>
    43.         /// <param name="action">Action to Invoke</param>
    44.         public static void RunAsync(Action action)
    45.         {
    46.             ThreadPool.QueueUserWorkItem(_ => action?.Invoke());
    47.         }
    48.         /// <summary>
    49.         /// Runs Action with State on Thread
    50.         /// </summary>
    51.         /// <param name="action">Action to Invoke</param>
    52.         /// <param name="state">State for Action</param>
    53.         public static void RunAsync(Action<object> action, object state)
    54.         {
    55.             ThreadPool.QueueUserWorkItem(_ => action?.Invoke(state));
    56.         }
    57.         /// <summary>
    58.         /// Runs Action with State on Thread
    59.         /// </summary>
    60.         /// <param name="action">Action to Invoke</param>
    61.         /// <param name="state">State for Action</param>
    62.         public static void RunAsync(Action<object[]> action, params object[] state)
    63.         {
    64.             ThreadPool.QueueUserWorkItem(_ => action?.Invoke(state));
    65.         }
    66.         #endregion
    67.  
    68.         #region MainThread
    69.         /// <summary>
    70.         /// Runs Action on the Unity Main Thread
    71.         /// </summary>
    72.         /// <param name="action">Action to Invoke</param>
    73.         public static void RunOnMainThread(Action action)
    74.         {
    75.             lock (_simpleActionBacklog)
    76.             {
    77.                 _simpleActionBacklog.Add(action);
    78.                 _hasWork = true;
    79.             }
    80.         }
    81.  
    82.         /// <summary>
    83.         /// Runs an Action with Parameters on the Unity Main Thread
    84.         /// </summary>
    85.         /// <param name="action">Action to Invoke</param>
    86.         /// <param name="parameters">Parameters for Action</param>
    87.         public static void RunOnMainThread(Action<object[]> action, params object[] parameters)
    88.         {
    89.             lock (_complexActionBacklog)
    90.             {
    91.                 _complexActionBacklog.Add(action, parameters);
    92.                 _hasWork = true;
    93.             }
    94.         }
    95.         #endregion
    96.  
    97.         #region Coroutine
    98.         /// <summary>
    99.         /// Runs Coroutine on Dispatcher-GameObject
    100.         /// </summary>
    101.         /// <param name="routine">Coroutine</param>
    102.         /// <returns>Routine-Reference</returns>
    103.         public static Coroutine RunCoroutine(IEnumerator routine) => _instance.StartCoroutine(routine);
    104.         /// <summary>
    105.         /// Runs Coroutine on Main Thread from any Thread
    106.         /// </summary>
    107.         /// <param name="routine">Coroutine to Run</param>
    108.         public static void RunCoroutineOnMainThread(IEnumerator routine) => RunOnMainThread(() => RunCoroutine(routine));
    109.         #endregion
    110.  
    111.         #region Private
    112.         /// <summary>
    113.         /// Creates & Initializes Dispatcher-Object when Game Loads
    114.         /// </summary>
    115.         [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    116.         private static void Initialize()
    117.         {
    118.             if (_instance == null)
    119.             {
    120.                 _instance = new GameObject("Dispatcher").AddComponent<Dispatcher>();
    121.                 DontDestroyOnLoad(_instance.gameObject);
    122.             }
    123.         }
    124.  
    125.         /// <summary>
    126.         /// Runs MainThread-Actions
    127.         /// </summary>
    128.         private void Update()
    129.         {
    130.             bool work = _hasWork;
    131.             if (work)
    132.             {
    133.                 if (_simpleActionBacklog.Count > 0)
    134.                 {
    135.                     Action[] arrSimple = null;
    136.                     lock (_simpleActionBacklog)
    137.                     {
    138.                         arrSimple = _simpleActionBacklog.ToArray();
    139.                         _simpleActionBacklog.Clear();
    140.                     }
    141.                     for (int i = 0; i < arrSimple.Length; i++)
    142.                         arrSimple[i].Invoke();
    143.                 }
    144.                 if (_complexActionBacklog.Count > 0)
    145.                 {
    146.                     KeyValuePair<Action<object[]>, object[]>[] arrComplex = null;
    147.                     lock (_complexActionBacklog)
    148.                     {
    149.                         arrComplex = _complexActionBacklog.ToArray();
    150.                         _complexActionBacklog.Clear();
    151.                     }
    152.                     for (int i = 0; i < arrComplex.Length; i++)
    153.                         arrComplex[i].Key.Invoke(arrComplex[i].Value);
    154.                 }
    155.                 _hasWork = false;
    156.             }
    157.         }
    158.         #endregion
    159.         #endregion
    160.     }
    161. }
    162.  
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    I think this is one of the few UnityEngine endpoints that you CAN hit from another thread. The catch is I'm pretty sure it won't appear in the console window until the main thread gets a paint.

    Do you mean it is fine in a built game, but that it crashes in editor?
     
  3. FrankvHoof

    FrankvHoof

    Joined:
    Nov 3, 2014
    Posts:
    258
    Yeah I didn't have logs dispatching before, but I figured that might be one of the things crashing the editor, which is why I added Dispatcher-code around my Debugs.
    I mean that the Unity-version will crash whilst a Console-version running in parallel with it will keep running (i.e. there's no error in deserialization or event-firing).
     
  4. FrankvHoof

    FrankvHoof

    Joined:
    Nov 3, 2014
    Posts:
    258
    Bumping this to see if anyone can share any insight
     
  5. FrankvHoof

    FrankvHoof

    Joined:
    Nov 3, 2014
    Posts:
    258