From e4af797a3b84389bab7d81ad62a024897767af0c Mon Sep 17 00:00:00 2001 From: Cory Leach Date: Tue, 27 Jun 2023 15:23:55 -0500 Subject: [PATCH 1/3] WaitForBackground fix Resolved an issue where awaiting on Awaiters.WaitForBackground could sometimes wait indefinitely. --- Runtime/Awaiters.cs | 43 ++++++++++------------------------ Tests/Runtime/AwaitersTests.cs | 30 ++++++++++++++++-------- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/Runtime/Awaiters.cs b/Runtime/Awaiters.cs index e1e6cc7..43f40ca 100644 --- a/Runtime/Awaiters.cs +++ b/Runtime/Awaiters.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using UnityEngine; namespace Gameframe.Async { @@ -9,17 +10,17 @@ public static class Awaiters private static readonly WaitForBackground _waitForBackground = new WaitForBackground(); private static readonly WaitForUnityThread _waitForUnityUnityThread = new WaitForUnityThread(); private static readonly WaitForUpdate _waitForUpdate = new WaitForUpdate(); - + /// /// Await this property to migrate an async method to a background thread /// public static WaitForBackground BackgroundThread => _waitForBackground; - + /// /// Await this property to migrate to the Unity main thread. /// public static WaitForUnityThread MainUnityThread => _waitForUnityUnityThread; - + /// /// Await this property to resume on the same context after the game has advanced a frame /// @@ -32,35 +33,19 @@ private class BackgroundThreadJoinAwaiter : AbstractThreadJoinAwaiter { public override void OnCompleted(Action continuation) { - if (isCompleted) - { - throw new InvalidOperationException("Continuation is invalid. This awaiter is already completed."); - } - _continuation = continuation; + Complete(); + Task.Run(continuation); } } - + public IAwaitable GetAwaiter() { - //Doing Task.Run(()=>{}).ConfigureAwait(false) will apparently sometimes still resume on the main thread - //Updated to the below pattern to ensure we actually will be running in the background when we resume - var awaiter = new BackgroundThreadJoinAwaiter(); - Task.Run(async () => - { - //Doing complete immediately without a yield appears to cause the awaiter to never resume - //I'm not entirely sure as to why. - //I suspected maybe Complete() was getting called before the the method doing the awaiting could add its continuation - //However when I added a check and exception for this I did not see it get thrown. - //Adding the await Task.Yield however appeared to get Unit tests to consistently pass. - await Task.Yield(); - awaiter.Complete(); - }); - return awaiter; + return new BackgroundThreadJoinAwaiter();; } } public class WaitForUnityThread - { + { private class MainThreadJoinAwaiter : AbstractThreadJoinAwaiter { public override void OnCompleted(Action continuation) @@ -69,7 +54,7 @@ public override void OnCompleted(Action continuation) } } - + public IAwaitable GetAwaiter() { var awaiter = new MainThreadJoinAwaiter(); @@ -77,7 +62,7 @@ public IAwaitable GetAwaiter() return awaiter; } } - + /// /// Awaitable class that will wait for the next frame of the game /// It starts a task on the main thread that yields and then returns @@ -93,7 +78,7 @@ private static async Task WaitForNextFrame() await Task.Yield(); } } - + /// /// Interface that implements all the properties needed to make an object awaitable /// @@ -102,7 +87,7 @@ public interface IAwaitable : INotifyCompletion bool IsCompleted { get; } void GetResult(); } - + /// /// Used internally to implement some custom await continuation logic /// @@ -130,5 +115,3 @@ public void GetResult() } } - - diff --git a/Tests/Runtime/AwaitersTests.cs b/Tests/Runtime/AwaitersTests.cs index fd1cbda..34b6093 100644 --- a/Tests/Runtime/AwaitersTests.cs +++ b/Tests/Runtime/AwaitersTests.cs @@ -9,49 +9,59 @@ namespace Gameframe.Async.Tests { public class AwaitersTests { - [UnityTest, Timeout(1000)] + [UnityTest, Timeout(10000)] public IEnumerator TestBackgroundAndMainThreadAwaiters() { yield return null; var task = DoTest(); yield return task.AsIEnumerator(); } - + private static async Task DoTest() { Assert.IsTrue(UnityTaskUtil.UnitySynchronizationContext != null); - + //Start on unity thread Assert.IsTrue(UnityTaskUtil.CurrentThreadIsUnityThread); //Test Migration Multiple times just to be sure + Debug.Log("-- Loop 10 Times --"); for (int i = 0; i < 10; i++) { //Migrate to background thread - await Awaiters.BackgroundThread; + Debug.Log("Await Background"); + await Awaiters.BackgroundThread; Assert.IsFalse(UnityTaskUtil.CurrentThreadIsUnityThread,$"Expected to be on background thread. CurrentThread:{Thread.CurrentThread.ManagedThreadId} UnityThreadId:{UnityTaskUtil.UnityThreadId}"); - + //Migrate back to main thread + Debug.Log("Await Main"); await Awaiters.MainUnityThread; Assert.IsTrue(UnityTaskUtil.CurrentThreadIsUnityThread,$"Expected to be on main thread. CurrentThread:{Thread.CurrentThread.ManagedThreadId} UnityThreadId:{UnityTaskUtil.UnityThreadId}"); } - + Debug.Log("-- Loop Complete --"); + //Await the main thread when already on the main thread should do nothing + Assert.IsTrue(UnityTaskUtil.CurrentThreadIsUnityThread,$"Expected to be on main thread. CurrentThread:{Thread.CurrentThread.ManagedThreadId} UnityThreadId:{UnityTaskUtil.UnityThreadId}"); + Debug.Log("Await Main"); await Awaiters.MainUnityThread; Assert.IsTrue(UnityTaskUtil.CurrentThreadIsUnityThread); - + //Await the next frame int frame = Time.frameCount; + Debug.Log("Await Next Frame"); await Awaiters.NextFrame; Assert.IsTrue(UnityTaskUtil.CurrentThreadIsUnityThread); Assert.IsTrue(Time.frameCount == frame+1); - + //Test can await next frame from background thread and then resume on background thread + Debug.Log("Await Background"); await Awaiters.BackgroundThread; Assert.IsFalse(UnityTaskUtil.CurrentThreadIsUnityThread); + Debug.Log("Await Next Frame"); await Awaiters.NextFrame; + //Confirm we're still on the background thread Assert.IsFalse(UnityTaskUtil.CurrentThreadIsUnityThread); } - + } -} \ No newline at end of file +} From 5f96948d8b23f26fc6f2a03d40d8d3b41d2580cb Mon Sep 17 00:00:00 2001 From: Cory Leach Date: Tue, 27 Jun 2023 15:29:52 -0500 Subject: [PATCH 2/3] package version bump --- README.md | 14 ++++++-------- package.json | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 990399a..b8409ef 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,10 @@

Gameframe.Async 👋

-[![Build Status](https://travis-ci.org/coryleach/UnityAsync.svg?branch=master)](https://travis-ci.org/coryleach/UnityAsync) -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/d2749fdbc70f422a9d1efccb56d48bff)](https://www.codacy.com/manual/coryleach/UnityAsync?utm_source=github.com&utm_medium=referral&utm_content=coryleach/UnityAsync&utm_campaign=Badge_Grade) ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/coryleach/UnityAsync?include_prereleases) [![openupm](https://img.shields.io/npm/v/com.gameframe.async?label=openupm&registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.gameframe.async/) -[![license](https://img.shields.io/github/license/coryleach/UnityAsync)](https://github.com/coryleach/UnityAsync/blob/master/LICENSE) - +![version](https://img.shields.io/github/package-json/v/coryleach/{PACKAGE.REPOSITORYNAME}) +[![license](https://img.shields.io/github/license/coryleach/{PACKAGE.REPOSITORYNAME})](https://github.com/coryleach/{PACKAGE.REPOSITORYNAME}/blob/master/LICENSE) [![twitter](https://img.shields.io/twitter/follow/coryleach.svg?style=social)](https://twitter.com/coryleach) @@ -22,7 +20,7 @@ #### Using UnityPackageManager (for Unity 2019.3 or later) Open the package manager window (menu: Window > Package Manager)
Select "Add package from git URL...", fill in the pop-up with the following link:
-https://github.com/coryleach/UnityAsync.git#1.0.4
+https://github.com/coryleach/UnityAsync.git#1.0.5
#### Using UnityPackageManager (for Unity 2019.1 or later) @@ -30,14 +28,14 @@ Find the manifest.json file in the Packages folder of your project and edit it t ```js { "dependencies": { - "com.gameframe.async": "https://github.com/coryleach/UnityAsync.git#1.0.4", + "com.gameframe.async": "https://github.com/coryleach/UnityAsync.git#1.0.5", ... }, } ``` - @@ -95,8 +93,8 @@ await Awaiters.MainUnityThread; ## Show your support - Give a ⭐️ if this project helped you! +{AUTHOR.KOFI} *** _This README was generated with ❤️ by [Gameframe.Packages](https://github.com/coryleach/unitypackages)_ diff --git a/package.json b/package.json index 50c732d..9f55811 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.gameframe.async", "displayName": "Gameframe.Async", - "version": "1.0.4", + "version": "1.0.5", "description": "> Async task utility package for Unity \n> Helper methods for starting tasks on the Unity thread. \n> Start and await coroutines from any thread.", "keywords": [], "author": { From 2a2bd647ae98e5b6d12f7bef9b296236aa8b46ae Mon Sep 17 00:00:00 2001 From: Cory Leach of Legends Date: Tue, 27 Jun 2023 15:32:33 -0500 Subject: [PATCH 3/3] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index b8409ef..447f654 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,7 @@ ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/coryleach/UnityAsync?include_prereleases) [![openupm](https://img.shields.io/npm/v/com.gameframe.async?label=openupm&registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.gameframe.async/) -![version](https://img.shields.io/github/package-json/v/coryleach/{PACKAGE.REPOSITORYNAME}) -[![license](https://img.shields.io/github/license/coryleach/{PACKAGE.REPOSITORYNAME})](https://github.com/coryleach/{PACKAGE.REPOSITORYNAME}/blob/master/LICENSE) +[![license](https://img.shields.io/github/license/coryleach/UnityAsync)](https://github.com/coryleach/UnityAsync/blob/master/LICENSE) [![twitter](https://img.shields.io/twitter/follow/coryleach.svg?style=social)](https://twitter.com/coryleach)