I’m trying to use a NamedPipe IPC to read data passed to my Windows Standalone build as to simulate a “deeplink”. In order to do so, I start a simple thread that waits until the Pipe Server sends data to the Pipe Client, and when it does, I invoke an event on the main thread to pass the data over (it only consists of a simple string).
The thread uses a while loop that runs until a CancellationToken requests cancellation, which I flag inside an OnDestroy.
I’m having trouble gracefully terminating the thread if the Editor is stopped. If I stop it, the next time I try to hit Play, it gets stuck “Reloading Domain” and forces me to termiante the program through the Task Manager. I did try waiting it out once, but gave up after 40 minutes had passed.
I haven’t tested this on an actual build yet, but I assume it’s not gonna be working either.
I’m guessing this is connected to my handling of the thread, I haven’t really used them until now. Maybe I’m not terminating it correctly? Can I somehow check if the thread is running after Unity stops?
I personally don’t really like how big chunks of code look on here, so I also created a public gist with my code.
namespace DeepLinks
{
[DisallowMultipleComponent]
public class DeepLinkListener : MonoBehaviour
{
// Singleton
public static DeepLinkListener Instance { get; private set; }
protected virtual void Awake()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
Instance = this;
}
public static event Action<string> OnDeepLinkReceived = delegate { };
private const string PIPE_NAME = "pipeName";
private PipeListener pipeListener;
private void Start()
{
pipeListener = new PipeListener(PIPE_NAME);
pipeListener.OnDeepLinkReceived += _arg => OnDeepLinkReceived?.Invoke((string)_arg);
try
{
pipeListener.StartListening();
}
catch (Exception e)
{
Debug.LogError("Disposing deeplink listener. " + e.Message);
pipeListener?.Dispose();
}
}
private void OnDestroy()
{
print("disposing thread");
pipeListener?.Dispose();
}
}
public class PipeListener : IDisposable
{
public event Action<object> OnDeepLinkReceived = delegate { };
public event Action<Exception> OnThreadAborted = delegate { };
private const int READ_INTERVAL_MS = 500;
private static volatile SynchronizationContext mainThreadContext;
private volatile CancellationTokenSource cancellationTokenSource = new();
private Thread listenerThread;
private NamedPipeServerStream pipe;
private StreamReader reader;
private string pipeName;
private bool isRunning;
public PipeListener(string _pipeName)
{
if (string.IsNullOrEmpty(_pipeName))
throw new ArgumentException("_pipeName is null.");
pipeName = _pipeName;
mainThreadContext = SynchronizationContext.Current;
// Restart listening after a deeplink is received
OnDeepLinkReceived += StartListening;
// Also restart listening if an error occurred
OnThreadAborted += StartListening;
}
private void StartListening(object _) => StartListening();
public async void StartListening()
{
// Clean up any previous threads
if (listenerThread != null)
{
Dispose();
await UniTask.WaitUntil(() => listenerThread == null || listenerThread.ThreadState
is ThreadState.Stopped
or ThreadState.Aborted
or ThreadState.Unstarted);
}
listenerThread = new Thread(ListenToPipe)
{
IsBackground = true,
Name = "Deep Link Listener",
Priority = System.Threading.ThreadPriority.Lowest
};
listenerThread.Start();
}
private void ListenToPipe()
{
cancellationTokenSource = new CancellationTokenSource();
isRunning = true;
while (isRunning && !cancellationTokenSource.Token.IsCancellationRequested)
{
using (pipe = new NamedPipeServerStream(pipeName, PipeDirection.In))
{
pipe.WaitForConnection();
reader = new StreamReader(pipe);
try
{
string data = reader.ReadLine();
if (!string.IsNullOrEmpty(data))
{
SendToMainThread(OnDeepLinkReceived, data);
}
}
catch (Exception e) // If anything breaks, just clean the thread and warn the caller
{
Debug.LogError("Pipe Thread encountered Exception. Calling OnThreadAborted, which should restart the thread");
SendToMainThread(OnThreadAborted, e);
break;
}
}
//Thread.Sleep(READ_INTERVAL_MS);
}
isRunning = false;
reader?.Close();
pipe?.Close();
Debug.LogError("Thread has finished");
}
public static void SendToMainThread<T>(Action<T> _action, T _parameter)
{
mainThreadContext.Post(_ => _action(_parameter), null);
}
public void Dispose()
{
isRunning = false;
cancellationTokenSource?.Cancel();
cancellationTokenSource?.Dispose();
Debug.LogError("Disposed thread, waiting for it to finish.");
//listenerThread?.Abort();
}
}
}
I’d be grateful if anyone can help. Thanks.