Do you have plans to provide proper SynchronizationContext for main thread or users will be expected to do it themselves?
This is not something we have considered yet. Thanks for bringing it up though. I’ll start a discussion internally about it.
Don’t forget to setup a synchronization context for edit-time as well, so that editor extensions could use async/await too.
In current version i was able to put together a simple synchronization context that dispatches everything to the next update, and i have to say async await syntax in UI looks glorious, both in multithreaded situations and cases when you just need to queue one animation after another.
I’ve been playing with async/await for quite some time by now in my C# 6 integration hack project. I made there four separate synchronization contexts: for update, late update, fixed update and for the editor update. Not sure it’s the best idea, but seems quite useful:
- When you launch an async method from a FixedUpdate handler, you would probably expect to return back to FixedUpdate context when the async method finishes.
- Another example is when you do something in the Update context and then want to switch to FixedUpdate for some reason. If there are multiple synchronization contexts, it’s very easy to do:
Example
private async void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
await AsyncTools.ToFixedUpdate(); // switch to FixedUpdate context
var rb = GetComponent<Rigidbody>();
var elapsedTime = 0f;
while (elapsedTime < 1)
{
elapsedTime += Time.deltaTime;
rb.AddForce(Vector3.up * 15);
await 0; // wait for 0 seconds; since we are in FixedUpdate context, this effectively means wait for the next physics frame
}
await AsyncTools.ToUpdate(); // switch to Update context
// do something in Update context
}
}
On the other hand, maybe it’s overkill. We don’t have this functionality now (except WaitForFixedUpdate) and I haven’t seen any complaints.
–
I checked if the demo scenes and scripts from my C# 6 integration project work in this mono 4.6 preview, and they do work fine. So, here is a demo project that contains SynchronizationContexts, corresponding TaskSchedulers, a bunch of awaiters to make life easier, and also demo scripts and scenes for all new C# 5.0 and 6.0 features, including usage of Tasks and async/await in different combinations.
2805759–203744–Mono Runtime Upgrade Playground.zip (86.1 KB)
Yeah, especially considering that coroutines can’t be event handlers (their signatures are not compatible with UnityAction delegate) while async methods can:
public async void OnClickHandler()
{
button.interactable = false;
await DoStuff();
button.interactable = true;
}
@alexzzzz it looks really good, but maybe a bit to complicated to be included directly into engine, having this as a separate code would have been great, i’ll have a better look at your project tomorrow.
SynchronizationContext absolutely must be provided by Unity.
Imagine, you write a complex code-heavy asset where you want to use async/await. If the context is not guaranteed, you either setup your own one or don’t use async/await at all. If you do setup your own context, there could be a conflict with some other complex asset whose author also decided to provide a synchronization context for their own needs. The asset creators will fight each other for the right to setup the context.
I agree that unity should have its own synchronization context to properly utilize the functionality of async/await.
This looks really nice. The consensus on our side is that Unity will provide a SynchronizationContext. I’m not sure if we’ll get it into the next preview drop but it should be coming soon.
I’m not an expert on the async stuff but if unity has their own context does that mean we could potentially do stuff like:
await UnityAsync.WaitForFixedUpdate();
effectively replacing coroutines? If so that would be really amazing - coroutines needed some love and suddenly if that works in async we get coroutines on steroids ![]()
EDIT: Reading alexz’ post seems that is possible indeed, hurrah!
Are you considering a synchronization context on the GameObject level?
If there is a single SynchronizationContext GameObject every await would need to check if the current GameObject wasn’t destroyed.
I guess I should be able to use something like ConfigureAwait, so I can set the continuation to run even if the GameObject is no longer there (or even survive a scene change), but it should not be the default behavior.
Just like when I use StartCoroutine and it runs on the current MonoBehaviour, await should do the same.
And a ConfigureAwait extension could let me configure it to
- continue even if the gameObject is not active (optionally wait for the GameObject to became active)
- continue even if the gameObject was destroyed
- continue even if the scene was changed
- resume on update / lateUpdate / fixedUpdate
Something like:
ConfigureAwait(AsyncContext context, UpdateStep step)
enum AsyncContext {
ActiveBehaviour, // Default, wait until this MonoBehaviour is enabled to resume
Behaviour, // Resume even if the MonoBehaviour is not enabled (behaviour was not destroyed)
GameObject, // Runs even if the GameObject is not active (gameObject was not destroyed)
Scene, // Runs only if the scene is still active (scene was not destroyed)
Game // Resume ignoring scene changes (game is still running)
}
enum UpdateStep {
Update, // Default
FixedUpdate,
LateUpdate
}
and by not using ConfigureAwait it would use AsyncContext.ActiveBehaviour and resume on UpdateStep.Update.
I think that synchronization context should not be bound to any gameobject or scene for that matter
This is a huge deal, it sucks to have to make something a mono behavior, and ensure the Gameobject it is attached to doesn’t get destroyed etc just to have coroutine functionality.
I like jvppm_pf’s proposal. It’s default reflects what people have come to expect from coroutines, but allows the flexibility to associate with the current scene or game. Synchronization contexts are exactly what their name says, contextual. Making them contextual to an active behaviour seems logical to me in an application current designed around code [almost] always running in the context of an active behaviour.
I’m so excited for this upgrade btw. Love what you guys are doing.
I’m sharing this with you guys, it uses async and Task; it’s working great for me now.
NOTE:
- Used with v5.5.0b7
- This is for the editor as I needed to run long operations within an editor window without blocking
- I guess it could be upgraded for play mode but it has to be done
(with a hidden GO/MonoBehaviour singleton instead of EditorApplication.Update ?)
Improvements from the community are welcome !
The context:
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using UnityEditor;
namespace Editor
{
[InitializeOnLoad]
public sealed class UnitySynchronizationContext : SynchronizationContext
{
private static readonly ConcurrentQueue<Message> Queue;
static UnitySynchronizationContext()
{
Queue = new ConcurrentQueue<Message>();
EditorApplication.update += Update;
}
private static void Enqueue(SendOrPostCallback d, object state)
{
Queue.Enqueue(new Message(d, state));
}
private static void Update()
{
if (!Queue.Any())
return;
Message message;
if (!Queue.TryDequeue(out message))
return;
message.Callback(message.State);
}
public override SynchronizationContext CreateCopy()
{
return new UnitySynchronizationContext();
}
public override void Post(SendOrPostCallback d, object state)
{
Enqueue(d, state);
}
public override void Send(SendOrPostCallback d, object state)
{
Enqueue(d, state);
}
private sealed class Message
{
public Message(SendOrPostCallback callback, object state)
{
Callback = callback;
State = state;
}
public SendOrPostCallback Callback { get; set; }
public object State { get; set; }
}
}
}
The helper class:
using System;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using UnityEditor;
namespace Editor
{
[InitializeOnLoad]
public static class UnityTasks
{
private static readonly TaskScheduler Scheduler;
static UnityTasks()
{
var context = new UnitySynchronizationContext();
SynchronizationContext.SetSynchronizationContext(context);
Scheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
public static Task Run([NotNull] Func<Task> func)
{
if (func == null)
throw new ArgumentNullException("func");
var task = Task.Factory
.StartNew(func, CancellationToken.None, TaskCreationOptions.DenyChildAttach, Scheduler)
.Unwrap();
return task;
}
public static Task<T> Run<T>([NotNull] Func<Task<T>> func)
{
if (func == null)
throw new ArgumentNullException("func");
var task = Task.Factory
.StartNew(func, CancellationToken.None, TaskCreationOptions.DenyChildAttach, Scheduler)
.Unwrap();
return task;
}
public static Task Run([NotNull] Action action)
{
if (action == null)
throw new ArgumentNullException("action");
var task = Task.Factory
.StartNew(action, CancellationToken.None, TaskCreationOptions.DenyChildAttach, Scheduler);
return task;
}
public static Task<T> Run<T>([NotNull] Func<T> func)
{
if (func == null)
throw new ArgumentNullException("func");
var task = Task.Factory
.StartNew(func, CancellationToken.None, TaskCreationOptions.DenyChildAttach, Scheduler);
return task;
}
}
}
An example:
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
namespace Editor
{
public class MyWindow : EditorWindow
{
[MenuItem("TEST/MyWindow")]
private static void Init()
{
GetWindow<MyWindow>();
}
private async void OnGUI()
{
if (GUILayout.Button("Async"))
{
await UnityTasks.Run(async () =>
{
for (var i = 0; i < 10; i++)
{
await Task.Delay(100);
EditorUtility.CreateGameObjectWithHideFlags(i.ToString(), HideFlags.None);
}
});
}
}
}
}
Sorry, I didn’t read your first post well … basically you’ve nailed it !
Thanks for sharing this ![]()
This is great. I tried to get synchronization contexts like this working at my previous job for an-inter process comm system. I eventually wound up scrapping my “tasks” implementation for a a lock-with-queue implementation, since Unity lacked the context support. Had this been in before we could of had the unity client and WPF server run the exact same async/await logic for processing the data packets.
I hope someone could point us to a place where we can track the progress on this feature, perhaps on the issue tracker? I don’t see anything about this on the roadmap.
We don’t have anything on the Issue tracker yet. On the roadmap, this is listed as “.NET Profile Upgrade” in the “Research” section. It has been there for a while though, so that won’t give you any sense of progress.
We’re hoping to have another preview build, this time with a number of players, ready within the next few weeks. This build won’t have anything specific to the SyncContext yet though, as we’ve not worked on that issue. However, it is coming up on our internal list of priorities.
