I am working on a system to run tasks on background threads (non unity main thread) but need the ability to run code on the main thread intermittently. I found that I can use the Task.Factory
to launch a Task
on a specific TaskScheduler
and that I can get a reference to such a scheduler from the UnitySynchronizationContext
which runs all tasks on the Unity main thread. This seems to work fine in this example below where a main thread task is launched from a background task:
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
public class AsyncExploration : MonoBehaviour
{
private TaskScheduler unityScheduler;
void Awake()
{
Debug.Log($"Main Thread is id {Thread.CurrentThread.ManagedThreadId}/{System.Environment.CurrentManagedThreadId}");
// capturing reference
unityScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
void Start()
{
// Task running in Default Task Scheduler
Task.Run(() => {
Debug.Log($"Random task running on thread {Thread.CurrentThread.ManagedThreadId}");
// Starting Task using the Unity Scheduler
Task.Factory.StartNew(() => {
Debug.Log($"Inner task with unity scheduler running on thread {Thread.CurrentThread.ManagedThreadId}");
}, CancellationToken.None, TaskCreationOptions.None, unityScheduler);
});
}
}
With the output:
> Main Thread is id 1/1
> Random task running on thread 966
> Inner task with unity scheduler running on thread 1
Can such a reference stored in a global variable be used to reliably run tasks on the main thread or are there any problems or downsides I am potentially overlooking?
It's perfectly fine caching the TaskScheduler returned from TaskScheduler.FromCurrentSynchronizationContext()
or the SynchronizationContext
itself.
Why?
Consider this code
// UI Thread
// Here SynchronizationContext.Current is captured
await Task.Run(async () =>
{
// Here we are on the ThreadPool
await Task.Delay(1000);
});
// Here SynchronizationContext.Current is restored
// Ui Thread
Something();
When you await a Task your current SynchronizationContext
is captured (i.e. is saved into a variable), and it will be used to run the continuation.
Note That Task can run indefinitely long! So that Synchronization Context should be valid indefinitely long!
Can you cache TaskScheduler.FromCurrentSynchronizationContext()
?
If you take a look at the source code, you'll se that TaskScheduler.FromCurrentSynchronizationContext()
returns an instance of SynchronizationContextTaskScheduler
.
SynchronizationContext
.QueueTask()
method (That is the method is called when you schedule a Task using Task.Factory.StartNew(actionDoSchedule,..,..,taskScheduler)
), just uses that SynchronizationContext to execute the Task.So the TaskScheduler is valid until the SynchronizationContext is valid. But the SynchronizationContext is always valid!