จัดเตรียมการจัดสรรที่มีประสิทธิภาพฟรี Async/รอการรวมเข้าด้วยกันเพื่อความสามัคคี
UniTask<T>
และ Custom AsyncMethodbuilder เพื่อให้ได้การจัดสรรเป็นศูนย์UniTask.Yield
, UniTask.Delay
, UniTask.DelayFrame
ฯลฯ ) ที่เปิดใช้งานการแทนที่การดำเนินการ coroutine ทั้งหมด สำหรับรายละเอียดทางเทคนิคดูโพสต์บล็อก: Unitask V2 - Zero Allocation Async/รอความสามัคคีด้วย LINQ แบบอะซิงโครนัส
สำหรับเคล็ดลับขั้นสูงดูโพสต์บล็อก: ขยาย Unitywebrequest ผ่านรูปแบบ Async Decorator - เทคนิคขั้นสูงของ UnitASK
ติดตั้งผ่านแพ็คเกจ UPM ที่มีการอ้างอิง GIT หรือแพ็คเกจสินทรัพย์ ( UniTask.*.*.*.unitypackage
) มีอยู่ใน UnitASK/Releases
// extension awaiter/methods can be used by this namespace
using Cysharp . Threading . Tasks ;
// You can return type as struct UniTask<T>(or UniTask), it is unity specialized lightweight alternative of Task<T>
// zero allocation and fast excution for zero overhead async/await integrate with Unity
async UniTask < string > DemoAsync ( )
{
// You can await Unity's AsyncObject
var asset = await Resources . LoadAsync < TextAsset > ( " foo " ) ;
var txt = ( await UnityWebRequest . Get ( " https://... " ) . SendWebRequest ( ) ) . downloadHandler . text ;
await SceneManager . LoadSceneAsync ( " scene2 " ) ;
// .WithCancellation enables Cancel, GetCancellationTokenOnDestroy synchornizes with lifetime of GameObject
// after Unity 2022.2, you can use `destroyCancellationToken` in MonoBehaviour
var asset2 = await Resources . LoadAsync < TextAsset > ( " bar " ) . WithCancellation ( this . GetCancellationTokenOnDestroy ( ) ) ;
// .ToUniTask accepts progress callback(and all options), Progress.Create is a lightweight alternative of IProgress<T>
var asset3 = await Resources . LoadAsync < TextAsset > ( " baz " ) . ToUniTask ( Progress . Create < float > ( x => Debug . Log ( x ) ) ) ;
// await frame-based operation like a coroutine
await UniTask . DelayFrame ( 100 ) ;
// replacement of yield return new WaitForSeconds/WaitForSecondsRealtime
await UniTask . Delay ( TimeSpan . FromSeconds ( 10 ) , ignoreTimeScale : false ) ;
// yield any playerloop timing(PreUpdate, Update, LateUpdate, etc...)
await UniTask . Yield ( PlayerLoopTiming . PreLateUpdate ) ;
// replacement of yield return null
await UniTask . Yield ( ) ;
await UniTask . NextFrame ( ) ;
// replacement of WaitForEndOfFrame
# if UNITY_2023_1_OR_NEWER
await UniTask . WaitForEndOfFrame ( ) ;
# else
// requires MonoBehaviour(CoroutineRunner))
await UniTask . WaitForEndOfFrame ( this ) ; // this is MonoBehaviour
#endif
// replacement of yield return new WaitForFixedUpdate(same as UniTask.Yield(PlayerLoopTiming.FixedUpdate))
await UniTask . WaitForFixedUpdate ( ) ;
// replacement of yield return WaitUntil
await UniTask . WaitUntil ( ( ) => isActive == false ) ;
// special helper of WaitUntil
await UniTask . WaitUntilValueChanged ( this , x => x . isActive ) ;
// You can await IEnumerator coroutines
await FooCoroutineEnumerator ( ) ;
// You can await a standard task
await Task . Run ( ( ) => 100 ) ;
// Multithreading, run on ThreadPool under this code
await UniTask . SwitchToThreadPool ( ) ;
/* work on ThreadPool */
// return to MainThread(same as `ObserveOnMainThread` in UniRx)
await UniTask . SwitchToMainThread ( ) ;
// get async webrequest
async UniTask < string > GetTextAsync ( UnityWebRequest req )
{
var op = await req . SendWebRequest ( ) ;
return op . downloadHandler . text ;
}
var task1 = GetTextAsync ( UnityWebRequest . Get ( " http://google.com " ) ) ;
var task2 = GetTextAsync ( UnityWebRequest . Get ( " http://bing.com " ) ) ;
var task3 = GetTextAsync ( UnityWebRequest . Get ( " http://yahoo.com " ) ) ;
// concurrent async-wait and get results easily by tuple syntax
var ( google , bing , yahoo ) = await UniTask . WhenAll ( task1 , task2 , task3 ) ;
// shorthand of WhenAll, tuple can await directly
var ( google2 , bing2 , yahoo2 ) = await ( task1 , task2 , task3 ) ;
// return async-value.(or you can use `UniTask`(no result), `UniTaskVoid`(fire and forget)).
return ( asset as TextAsset ) ? . text ?? throw new InvalidOperationException ( " Asset not found " ) ;
}
คุณสมบัติของ UnitAsk ขึ้นอยู่กับ C# 7.0 (คุณลักษณะตัวสร้างวิธี Async แบบกำหนดเองเหมือนงาน) ดังนั้นเวอร์ชัน Unity ที่ต้องการคือหลังจาก Unity 2018.3
รุ่นที่รองรับต่ำสุดอย่างเป็นทางการคือ Unity 2018.4.13f1
เหตุใดจึงจำเป็นต้องใช้ unitask (วัตถุเหมือนงานที่กำหนดเอง)? เพราะงานหนักเกินไปและไม่ตรงกับเธรด Unity (เธรดเดี่ยว) UnitASK ไม่ได้ใช้เธรดและการซิงโครไนซ์คอนเทสต์/ExecutionContext เนื่องจากวัตถุอะซิงโครนัสของ Unity นั้นถูกส่งโดยอัตโนมัติโดยเลเยอร์เครื่องยนต์ของ Unity มันประสบความสำเร็จเร็วขึ้นและลดการจัดสรรและรวมเข้ากับความสามัคคีอย่างสมบูรณ์
คุณสามารถรอ AsyncOperation
, ResourceRequest
, AssetBundleRequest
, AssetBundleCreateRequest
, UnityWebRequestAsyncOperation
, AsyncGPUReadbackRequest
, IEnumerator
และอื่น ๆ เมื่อ using Cysharp.Threading.Tasks;
-
Unitask ให้วิธีการขยายสามรูปแบบ
* await asyncOperation ;
* . WithCancellation ( CancellationToken ) ;
* . ToUniTask ( IProgress , PlayerLoopTiming , CancellationToken ) ;
WithCancellation
เป็นเวอร์ชันง่าย ๆ ของ ToUniTask
ทั้งสอง return UniTask
สำหรับรายละเอียดของการยกเลิกโปรดดู: ส่วนการยกเลิกและการจัดการข้อยกเว้น
หมายเหตุ: รอโดยตรงจะถูกส่งคืนจากช่วงเวลาดั้งเดิมของ playerloop แต่มีการลงโทษและ tounitask จะถูกส่งคืนจาก playerlooptiming ที่ระบุ สำหรับรายละเอียดของเวลาดู: ส่วน Playerloop
หมายเหตุ: AssetBundLeRequest มี
asset
และallAssets
, เริ่มต้นการรอการคืนasset
หากคุณต้องการได้รับallAssets
คุณสามารถใช้วิธีAwaitForAllAssets()
ประเภทของ UniTask
สามารถใช้ยูทิลิตี้เช่น UniTask.WhenAll
, UniTask.WhenAny
, UniTask.WhenEach
พวกเขาเป็นเหมือน Task.WhenAll
/ Task.WhenAny
แต่ประเภทการส่งคืนจะมีประโยชน์มากกว่า พวกเขาส่งคืนค่า tuples เพื่อให้คุณสามารถแยกแยะผลลัพธ์แต่ละรายการและผ่านหลายประเภท
public async UniTaskVoid LoadManyAsync ( )
{
// parallel load.
var ( a , b , c ) = await UniTask . WhenAll (
LoadAsSprite ( " foo " ) ,
LoadAsSprite ( " bar " ) ,
LoadAsSprite ( " baz " ) ) ;
}
async UniTask < Sprite > LoadAsSprite ( string path )
{
var resource = await Resources . LoadAsync < Sprite > ( path ) ;
return ( resource as Sprite ) ;
}
หากคุณต้องการแปลงการโทรกลับเป็น UnitASK คุณสามารถใช้ UniTaskCompletionSource<T>
ซึ่งเป็นรุ่นที่มีน้ำหนักเบาของ TaskCompletionSource<T>
public UniTask < int > WrapByUniTaskCompletionSource ( )
{
var utcs = new UniTaskCompletionSource < int > ( ) ;
// when complete, call utcs.TrySetResult();
// when failed, call utcs.TrySetException();
// when cancel, call utcs.TrySetCanceled();
return utcs . Task ; //return UniTask<int>
}
คุณสามารถแปลงงาน -> UnitASK: AsUniTask
, UniTask
-> UniTask<AsyncUnit>
: AsAsyncUnitUniTask
, UniTask<T>
-> UniTask
: AsUniTask
UniTask<T>
-> ค่าแปลงของ UniTask
นั้นฟรี
หากคุณต้องการแปลง Async เป็น coroutine คุณสามารถใช้ .ToCoroutine()
สิ่งนี้มีประโยชน์หากคุณต้องการอนุญาตให้ใช้ระบบ coroutine เท่านั้น
UnitASK ไม่สามารถรอสองครั้ง นี่เป็นข้อ จำกัด ที่คล้ายคลึงกับ Valuetask/ivaluetasksource ที่แนะนำใน. NET Standard 2.1
การดำเนินการต่อไปนี้ไม่ควรดำเนินการในอินสแตนซ์ Valuetask:
- รออินสแตนซ์หลายครั้ง
- เรียก Astask หลายครั้ง
- ใช้. result หรือ. getawaiter (). getResult () เมื่อการดำเนินการยังไม่เสร็จสมบูรณ์หรือใช้หลายครั้ง
- การใช้เทคนิคเหล่านี้มากกว่าหนึ่งรายการเพื่อใช้อินสแตนซ์
หากคุณทำสิ่งใด ๆ ข้างต้นผลลัพธ์จะไม่ได้กำหนด
var task = UniTask . DelayFrame ( 10 ) ;
await task ;
await task ; // NG, throws Exception
จัดเก็บไปยังฟิลด์คลาสคุณสามารถใช้ UniTask.Lazy
ที่รองรับการโทรหลายครั้ง .Preserve()
อนุญาตให้มีการโทรหลายครั้ง (ผลลัพธ์แคชภายใน) สิ่งนี้มีประโยชน์เมื่อมีการโทรหลายครั้งในขอบเขตฟังก์ชัน
นอกจากนี้ UniTaskCompletionSource
สามารถรอหลายครั้งและรอผู้โทรหลายคน
วิธีการโรงงาน UnitAsk บางวิธีมี CancellationToken cancellationToken = default
นอกจากนี้การดำเนินการ async สำหรับ Unity มี WithCancellation(CancellationToken)
และ ToUniTask(..., CancellationToken cancellation = default)
วิธีการขยาย
คุณสามารถผ่าน CancellationToken
ไปยังพารามิเตอร์โดย CancellationTokenSource
มาตรฐาน
var cts = new CancellationTokenSource ( ) ;
cancelButton . onClick . AddListener ( ( ) =>
{
cts . Cancel ( ) ;
} ) ;
await UnityWebRequest . Get ( " http://google.co.jp " ) . SendWebRequest ( ) . WithCancellation ( cts . Token ) ;
await UniTask . DelayFrame ( 1000 , cancellationToken : cts . Token ) ;
CancellationToken สามารถสร้างขึ้นได้โดย CancellationTokenSource
หรือวิธีการขยายของ Monobehaviour GetCancellationTokenOnDestroy
// this CancellationToken lifecycle is same as GameObject.
await UniTask . DelayFrame ( 1000 , cancellationToken : this . GetCancellationTokenOnDestroy ( ) ) ;
สำหรับการยกเลิกการเผยแพร่วิธีการทั้งหมด async แนะนำให้ยอมรับ CancellationToken cancellationToken
ในอาร์กิวเมนต์สุดท้ายและผ่าน CancellationToken
จากรากหนึ่งไปอีกที่หนึ่ง
await FooAsync ( this . GetCancellationTokenOnDestroy ( ) ) ;
// ---
async UniTask FooAsync ( CancellationToken cancellationToken )
{
await BarAsync ( cancellationToken ) ;
}
async UniTask BarAsync ( CancellationToken cancellationToken )
{
await UniTask . Delay ( TimeSpan . FromSeconds ( 3 ) , cancellationToken ) ;
}
CancellationToken
หมายถึงวงจรชีวิตของ Async คุณสามารถถือวงจรชีวิตของคุณเองแทนการยกเลิกเริ่มต้นเริ่มต้น
public class MyBehaviour : MonoBehaviour
{
CancellationTokenSource disableCancellation = new CancellationTokenSource ( ) ;
CancellationTokenSource destroyCancellation = new CancellationTokenSource ( ) ;
private void OnEnable ( )
{
if ( disableCancellation != null )
{
disableCancellation . Dispose ( ) ;
}
disableCancellation = new CancellationTokenSource ( ) ;
}
private void OnDisable ( )
{
disableCancellation . Cancel ( ) ;
}
private void OnDestroy ( )
{
destroyCancellation . Cancel ( ) ;
destroyCancellation . Dispose ( ) ;
}
}
หลังจาก Unity 2022.2 Unity เพิ่ม CancellationToken ใน Monobehaviour.DestroyCancellationToken และ Application.exitCancellationToken
เมื่อตรวจพบการยกเลิกวิธีการทั้งหมดจะส่ง OperationCanceledException
และเผยแพร่ต้นน้ำ เมื่อข้อยกเว้น (ไม่ จำกัด เฉพาะ OperationCanceledException
) ไม่ได้รับการจัดการในวิธีการ Async มันจะถูกเผยแพร่ในที่สุดไปยัง UniTaskScheduler.UnobservedTaskException
พฤติกรรมเริ่มต้นของข้อยกเว้นที่ไม่ได้รับการยอมรับคือการเขียนบันทึกเป็นข้อยกเว้น ระดับบันทึกสามารถเปลี่ยนแปลงได้โดยใช้ UniTaskScheduler.UnobservedExceptionWriteLogType
หากคุณต้องการใช้พฤติกรรมที่กำหนดเองให้ตั้งค่าการดำเนินการเป็น UniTaskScheduler.UnobservedTaskException.
และ OperationCanceledException
เป็นข้อยกเว้นพิเศษนี่จะถูกละเว้นอย่างเงียบ ๆ ที่ UnobservedTaskException
หากคุณต้องการยกเลิกพฤติกรรมในวิธี Async Unitask ให้โยน OperationCanceledException
ด้วยตนเอง
public async UniTask < int > FooAsync ( )
{
await UniTask . Yield ( ) ;
throw new OperationCanceledException ( ) ;
}
หากคุณจัดการกับข้อยกเว้น แต่ต้องการเพิกเฉย (เผยแพร่ไปยังการจัดการการยกเลิกทั่วโลก) ให้ใช้ตัวกรองข้อยกเว้น
public async UniTask < int > BarAsync ( )
{
try
{
var x = await FooAsync ( ) ;
return x * 2 ;
}
catch ( Exception ex ) when ( ! ( ex is OperationCanceledException ) ) // when (ex is not OperationCanceledException) at C# 9.0
{
return - 1 ;
}
}
การขว้าง/catch OperationCanceledException
นั้นหนักเล็กน้อยดังนั้นหากประสิทธิภาพเป็นข้อกังวลให้ใช้ UniTask.SuppressCancellationThrow
เพื่อหลีกเลี่ยงการดำเนินการ มันกลับมา (bool IsCanceled, T Result)
แทนที่จะขว้าง
var ( isCanceled , _ ) = await UniTask . DelayFrame ( 10 , cancellationToken : cts . Token ) . SuppressCancellationThrow ( ) ;
if ( isCanceled )
{
// ...
}
หมายเหตุ: การปราบปรามเมื่อคุณโทรเข้าสู่วิธีการมากที่สุดโดยตรง มิฉะนั้นค่าส่งคืนจะถูกแปลง แต่ไปป์ไลน์ทั้งหมดจะไม่ระงับการโยน
คุณสมบัติบางอย่างที่ใช้ลูปผู้เล่นของ Unity เช่น UniTask.Yield
และ UniTask.Delay
ฯลฯ กำหนดสถานะ cancellationToken บนลูปผู้เล่น ซึ่งหมายความว่ามันไม่ได้ยกเลิกทันทีเมื่อ CancellationToken
ยิง
หากคุณต้องการเปลี่ยนพฤติกรรมนี้การยกเลิกจะทันทีให้ตั้งค่าสถานะการ cancelImmediately
ตั้งค่าสถานะเป็นอาร์กิวเมนต์
await UniTask . Yield ( cancellationToken , cancelImmediately : true ) ;
หมายเหตุ: การตั้งค่า cancelImmediately
เป็นจริงและตรวจจับการยกเลิกทันทีนั้นมีค่าใช้จ่ายสูงกว่าพฤติกรรมเริ่มต้น นี่เป็นเพราะมันใช้ CancellationToken.Register
; มันหนักกว่าการตรวจสอบ CancellationToken บน Loop ผู้เล่น
การหมดเวลาคือการเปลี่ยนแปลงของการยกเลิก คุณสามารถตั้งค่าการหมดเวลาโดย CancellationTokenSouce.CancelAfterSlim(TimeSpan)
และผ่านวิธีการยกเลิกไปยังวิธีการ async
var cts = new CancellationTokenSource ( ) ;
cts . CancelAfterSlim ( TimeSpan . FromSeconds ( 5 ) ) ; // 5sec timeout.
try
{
await UnityWebRequest . Get ( " http://foo " ) . SendWebRequest ( ) . WithCancellation ( cts . Token ) ;
}
catch ( OperationCanceledException ex )
{
if ( ex . CancellationToken == cts . Token )
{
UnityEngine . Debug . Log ( " Timeout " ) ;
}
}
CancellationTokenSouce.CancelAfter
เป็น API มาตรฐาน อย่างไรก็ตามในความสามัคคีคุณไม่ควรใช้เพราะมันขึ้นอยู่กับตัวจับเวลาเธรดCancelAfterSlim
เป็นวิธีการขยายของ UnitAsk โดยใช้ PlayerLoop แทน
หากคุณต้องการใช้การหมดเวลากับแหล่งที่มาของการยกเลิกอื่น ๆ ให้ใช้ CancellationTokenSource.CreateLinkedTokenSource
var cancelToken = new CancellationTokenSource ( ) ;
cancelButton . onClick . AddListener ( ( ) =>
{
cancelToken . Cancel ( ) ; // cancel from button click.
} ) ;
var timeoutToken = new CancellationTokenSource ( ) ;
timeoutToken . CancelAfterSlim ( TimeSpan . FromSeconds ( 5 ) ) ; // 5sec timeout.
try
{
// combine token
var linkedTokenSource = CancellationTokenSource . CreateLinkedTokenSource ( cancelToken . Token , timeoutToken . Token ) ;
await UnityWebRequest . Get ( " http://foo " ) . SendWebRequest ( ) . WithCancellation ( linkedTokenSource . Token ) ;
}
catch ( OperationCanceledException ex )
{
if ( timeoutToken . IsCancellationRequested )
{
UnityEngine . Debug . Log ( " Timeout. " ) ;
}
else if ( cancelToken . IsCancellationRequested )
{
UnityEngine . Debug . Log ( " Cancel clicked. " ) ;
}
}
ปรับให้เหมาะสมสำหรับการลดการจัดสรร CancellationTokenSource สำหรับการหมดเวลาต่อการโทรวิธี Async คุณสามารถใช้ TimeoutController
ของ UnitASK
TimeoutController timeoutController = new TimeoutController ( ) ; // setup to field for reuse.
async UniTask FooAsync ( )
{
try
{
// you can pass timeoutController.Timeout(TimeSpan) to cancellationToken.
await UnityWebRequest . Get ( " http://foo " ) . SendWebRequest ( )
. WithCancellation ( timeoutController . Timeout ( TimeSpan . FromSeconds ( 5 ) ) ) ;
timeoutController . Reset ( ) ; // call Reset(Stop timeout timer and ready for reuse) when succeed.
}
catch ( OperationCanceledException ex )
{
if ( timeoutController . IsTimeout ( ) )
{
UnityEngine . Debug . Log ( " timeout " ) ;
}
}
}
หากคุณต้องการใช้การหมดเวลากับแหล่งที่มาของการยกเลิกอื่น ๆ ให้ใช้ new TimeoutController(CancellationToken)
TimeoutController timeoutController ;
CancellationTokenSource clickCancelSource ;
void Start ( )
{
this . clickCancelSource = new CancellationTokenSource ( ) ;
this . timeoutController = new TimeoutController ( clickCancelSource ) ;
}
หมายเหตุ: UNITASK มี .Timeout
, .TimeoutWithoutException
วิธีการอย่างไรก็ตามถ้าเป็นไปได้อย่าใช้สิ่งเหล่านี้โปรดส่ง CancellationToken
เพราะ .Timeout
ทำงานจากงานภายนอกไม่สามารถหยุดงานที่หมดเวลาได้ .Timeout
หมายถึงละเว้นผลลัพธ์เมื่อหมดเวลา หากคุณผ่าน CancellationToken
ใช้วิธีนี้มันจะทำหน้าที่จากด้านในของงานดังนั้นจึงเป็นไปได้ที่จะหยุดงานที่กำลังทำงานอยู่
การดำเนินการ async บางอย่างสำหรับ Unity มี ToUniTask(IProgress<float> progress = null, ...)
วิธีการขยาย
var progress = Progress . Create < float > ( x => Debug . Log ( x ) ) ;
var request = await UnityWebRequest . Get ( " http://google.co.jp " )
. SendWebRequest ( )
. ToUniTask ( progress : progress ) ;
คุณไม่ควรใช้ new System.Progress<T>
เพราะมันทำให้เกิดการจัดสรรทุกครั้ง ใช้ Cysharp.Threading.Tasks.Progress
แทน โรงงานความคืบหน้านี้มีสองวิธีคือ Create
และ CreateOnlyValueChanged
CreateOnlyValueChanged
การโทรเฉพาะเมื่อค่าความคืบหน้ามีการเปลี่ยนแปลง
การใช้อินเทอร์เฟซ iProgress กับผู้โทรดีกว่าเนื่องจากไม่มีการจัดสรรแลมบ์ดา
public class Foo : MonoBehaviour , IProgress < float >
{
public void Report ( float value )
{
UnityEngine . Debug . Log ( value ) ;
}
public async UniTaskVoid WebRequest ( )
{
var request = await UnityWebRequest . Get ( " http://google.co.jp " )
. SendWebRequest ( )
. ToUniTask ( progress : this ) ; // pass this
}
}
Unitask ทำงานบน playerloop ที่กำหนดเอง วิธีการที่ใช้ PlayerLoop ของ Unitask (เช่น Delay
, DelayFrame
, asyncOperation.ToUniTask
ฯลฯ ... ) ยอมรับ PlayerLoopTiming
นี้
public enum PlayerLoopTiming
{
Initialization = 0 ,
LastInitialization = 1 ,
EarlyUpdate = 2 ,
LastEarlyUpdate = 3 ,
FixedUpdate = 4 ,
LastFixedUpdate = 5 ,
PreUpdate = 6 ,
LastPreUpdate = 7 ,
Update = 8 ,
LastUpdate = 9 ,
PreLateUpdate = 10 ,
LastPreLateUpdate = 11 ,
PostLateUpdate = 12 ,
LastPostLateUpdate = 13
# if UNITY_2020_2_OR_NEWER
TimeUpdate = 14 ,
LastTimeUpdate = 15 ,
#endif
}
มันระบุว่าเมื่อใดที่จะเรียกใช้คุณสามารถตรวจสอบ PlayerLoopList.md ไปยัง PlayerLoop เริ่มต้นของ Unity และ Loop Custom Custom ของ Unitask
PlayerLoopTiming.Update
คล้ายกับ yield return null
ใน coroutine แต่มันถูกเรียกก่อนการอัปเดต (การอัปเดตและเหตุการณ์ UGUI ScriptRunDelayedDynamicFrameRate
button.onclick ฯลฯ ... ) ถูกเรียกใช้ ScriptRunBehaviourUpdate
PlayerLoopTiming.FixedUpdate
คล้ายกับ WaitForFixedUpdate
PlayerLoopTiming.LastPostLateUpdate
ไม่เทียบเท่ากับyield return new WaitForEndOfFrame()
การรอคอยของ Coroutine ดูเหมือนว่าจะทำงานหลังจากที่ Playerloop เสร็จสิ้น วิธีการบางอย่างที่ต้องใช้ปลายเฟรมของ Coroutine (Texture2D.ReadPixels
,ScreenCapture.CaptureScreenshotAsTexture
,CommandBuffer
ฯลฯ ) ทำงานไม่ถูกต้องเมื่อถูกแทนที่ด้วย Async/Await ในกรณีเหล่านี้ผ่าน Monobehaviour (Coroutine Runnner) ไปยังUniTask.WaitForEndOfFrame
ตัวอย่างเช่นawait UniTask.WaitForEndOfFrame(this);
เป็นทางเลือกการจัดสรรที่มีน้ำหนักเบาฟรีของyield return new WaitForEndOfFrame()
หมายเหตุ: ใน Unity 2023.1 หรือใหม่กว่า
await UniTask.WaitForEndOfFrame();
ไม่ต้องการ monobehaviour อีกต่อไป มันใช้UnityEngine.Awaitable.EndOfFrameAsync
yield return null
และ UniTask.Yield
มีความคล้ายคลึงกัน แต่แตกต่างกัน yield return null
จะส่งคืนเฟรมถัดไปเสมอ แต่ UniTask.Yield
ส่งคืนถัดไปเรียกว่า นั่นคือ call UniTask.Yield(PlayerLoopTiming.Update)
บน PreUpdate
มันจะส่งคืนเฟรมเดียวกัน UniTask.NextFrame()
รับประกันผลตอบแทนเฟรมถัดไปคุณสามารถคาดหวังได้ว่าสิ่งนี้จะทำตัวเหมือนกันกับ yield return null
Unitask.yield (โดยไม่ต้องยกเลิก) เป็นประเภทพิเศษส่งคืน
YieldAwaitable
และวิ่งบน FieldRunner มันมีน้ำหนักเบาที่สุดและเร็วที่สุด
AsyncOperation
ถูกส่งคืนจากช่วงเวลาดั้งเดิม ตัวอย่างเช่น Await SceneManager.LoadSceneAsync
จะถูกส่งคืนจาก EarlyUpdate.UpdatePreloading
และหลังจากถูกเรียกแล้ว Start
ของฉากที่โหลดจะถูกเรียกจาก EarlyUpdate.ScriptRunDelayedStartupFrame
ยัง await UnityWebRequest
จะถูกส่งกลับจาก EarlyUpdate.ExecuteMainThreadJobs
ใน Unitask รอการใช้เวลาดั้งเดิมโดยตรงในขณะที่ WithCancellation
ToUniTask
ใช้เวลาที่กำหนด นี่มักจะไม่ใช่ปัญหาเฉพาะ แต่ด้วย LoadSceneAsync
มันทำให้เกิดลำดับการเริ่มต้นและความต่อเนื่องที่แตกต่างกันหลังจากรอ ดังนั้นขอแนะนำให้ไม่ใช้ LoadSceneAsync.ToUniTask
หมายเหตุ: เมื่อใช้ Unity 2023.1 หรือใหม่กว่าให้แน่ใจว่าคุณ
using UnityEngine;
ในการใช้คำสั่งของไฟล์ของคุณเมื่อทำงานกับUnityEngine.Awaitable
ใหม่วิธีการที่สามารถรอคอยได้เช่นSceneManager.LoadSceneAsync
สิ่งนี้จะช่วยป้องกันข้อผิดพลาดในการรวบรวมโดยหลีกเลี่ยงการใช้UnityEngine.AsyncOperation
เวอร์ชัน
ใน StackTrace คุณสามารถตรวจสอบว่ามันทำงานอยู่ที่ไหนใน Playerloop
โดยค่าเริ่มต้น playerloop ของ Unitask จะเริ่มต้นที่ [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
ลำดับที่วิธีการที่เรียกว่า beforesceneload นั้นเป็นแบบไม่ใช้งานดังนั้นหากคุณต้องการใช้ UnitAsk ในวิธีการอื่น ๆ BEFORESCENELOAD คุณควรพยายามเริ่มต้นก่อนหน้านี้
// AfterAssembliesLoaded is called before BeforeSceneLoad
[ RuntimeInitializeOnLoadMethod ( RuntimeInitializeLoadType . AfterAssembliesLoaded ) ]
public static void InitUniTaskLoop ( )
{
var loop = PlayerLoop . GetCurrentPlayerLoop ( ) ;
Cysharp . Threading . Tasks . PlayerLoopHelper . Initialize ( ref loop ) ;
}
หากคุณนำเข้าแพ็คเกจ Entities
ของ Unity นั่นจะรีเซ็ตลูปเครื่องเล่นที่กำหนดเองเป็นค่าเริ่มต้นที่ BeforeSceneLoad
และฉีดลูปของ ECS เมื่อวิธีการ UNITY CALLE วิธีการฉีดของ ECS หลังจากวิธีการเริ่มต้นของ UnitASK UnitASK จะไม่ทำงานอีกต่อไป
ในการแก้ปัญหานี้คุณสามารถเริ่มต้นใหม่ unitask playerloop หลังจาก ECS เริ่มต้น
// Get ECS Loop.
var playerLoop = ScriptBehaviourUpdateOrder . CurrentPlayerLoop ;
// Setup UniTask's PlayerLoop.
PlayerLoopHelper . Initialize ( ref playerLoop ) ;
คุณสามารถวินิจฉัยได้ว่า Loop ผู้เล่นของ Unitask พร้อมหรือไม่โดยเรียก PlayerLoopHelper.IsInjectedUniTaskPlayerLoop()
หรือไม่ และ PlayerLoopHelper.DumpCurrentPlayerLoop
บันทึก playerLoops ปัจจุบันทั้งหมดเป็นคอนโซล
void Start ( )
{
UnityEngine . Debug . Log ( " UniTaskPlayerLoop ready? " + PlayerLoopHelper . IsInjectedUniTaskPlayerLoop ( ) ) ;
PlayerLoopHelper . DumpCurrentPlayerLoop ( ) ;
}
คุณสามารถปรับค่าใช้จ่ายลูปได้เล็กน้อยโดยลบการฉีดผู้เล่นที่ไม่ได้ใช้งาน คุณสามารถโทรหา PlayerLoopHelper.Initialize(InjectPlayerLoopTimings)
ในการเริ่มต้น
var loop = PlayerLoop . GetCurrentPlayerLoop ( ) ;
PlayerLoopHelper . Initialize ( ref loop , InjectPlayerLoopTimings . Minimum ) ; // minimum is Update | FixedUpdate | LastPostLateUpdate
InjectPlayerLoopTimings
มีสามที่ตั้งไว้ล่วงหน้า All
และ Standard
ทั้งหมด (ทั้งหมดที่ไม่มีล่าสุดยกเว้น LastPostLateUpdate), Minimum
( Update | FixedUpdate | LastPostLateUpdate
) ค่าเริ่มต้นคือทั้งหมดและคุณสามารถรวมการกำหนดเวลาการฉีดที่กำหนดเองเช่น InjectPlayerLoopTimings.Update | InjectPlayerLoopTimings.FixedUpdate | InjectPlayerLoopTimings.PreLateUpdate
คุณสามารถทำผิดพลาดในการใช้ PlayerLoopTiming
ที่ไม่ได้ฉีดโดย Microsoft.Codeanalysis.bannedapianalyzers ตัวอย่างเช่นคุณสามารถตั้งค่า BannedSymbols.txt
เช่นนี้สำหรับ InjectPlayerLoopTimings.Minimum
F:Cysharp.Threading.Tasks.PlayerLoopTiming.Initialization; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastInitialization; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.EarlyUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastEarlyUpdate; Isn't injected this PlayerLoop in this project.d
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastFixedUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.PreUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastPreUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.PreLateUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastPreLateUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.PostLateUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.TimeUpdate; Isn't injected this PlayerLoop in this project.
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastTimeUpdate; Isn't injected this PlayerLoop in this project.
คุณสามารถกำหนดค่าความรุนแรง RS0030
เป็นข้อผิดพลาด
async void
เป็นระบบงาน C# มาตรฐานดังนั้นจึงไม่ทำงานบนระบบ UnitASK มันจะดีกว่าที่จะไม่ใช้มัน async UniTaskVoid
เป็นรุ่นที่มีน้ำหนักเบาของ async UniTask
เนื่องจากไม่สามารถรอเสร็จได้และรายงานข้อผิดพลาดทันทีไปยัง UniTaskScheduler.UnobservedTaskException
หากคุณไม่จำเป็นต้องรอ (ไฟและลืม) การใช้ UniTaskVoid
จะดีกว่า น่าเสียดายที่จะยกเลิกการเตือนคุณต้องโทร Forget()
public async UniTaskVoid FireAndForgetMethod ( )
{
// do anything...
await UniTask . Yield ( ) ;
}
public void Caller ( )
{
FireAndForgetMethod ( ) . Forget ( ) ;
}
นอกจากนี้ Unitask มีวิธี Forget
มันคล้ายกับ UniTaskVoid
และมีเอฟเฟกต์เดียวกัน อย่างไรก็ตาม UniTaskVoid
นั้นมีประสิทธิภาพมากขึ้นหากคุณไม่ได้ใช้ await
อย่างสมบูรณ์。
public async UniTask DoAsync ( )
{
// do anything...
await UniTask . Yield ( ) ;
}
public void Caller ( )
{
DoAsync ( ) . Forget ( ) ;
}
หากต้องการใช้ Async Lambda ที่ลงทะเบียนกับกิจกรรมอย่าใช้ async void
แต่คุณสามารถใช้ UniTask.Action
หรือ UniTask.UnityAction
ซึ่งทั้งสองอย่างสร้างผู้ได้รับมอบหมายผ่าน async UniTaskVoid
Lambda
Action actEvent ;
UnityAction unityEvent ; // especially used in uGUI
// Bad: async void
actEvent += async ( ) => { } ;
unityEvent += async ( ) => { } ;
// Ok: create Action delegate by lambda
actEvent += UniTask . Action ( async ( ) => { await UniTask . Yield ( ) ; } ) ;
unityEvent += UniTask . UnityAction ( async ( ) => { await UniTask . Yield ( ) ; } ) ;
UniTaskVoid
ยังสามารถใช้ในวิธี Start
ของ Monobehaviour
class Sample : MonoBehaviour
{
async UniTaskVoid Start ( )
{
// async init code.
}
}
มีประโยชน์สำหรับการตรวจสอบ (รั่วไหล) หน่วย คุณสามารถเปิดหน้าต่างตัวติดตามใน Window -> UniTask Tracker
UnitaskTracker มีไว้สำหรับการดีบักใช้งานเพื่อเปิดใช้งานการติดตามและการจับภาพ stacktraces นั้นมีประโยชน์ แต่มีผลกระทบอย่างหนัก การใช้งานที่แนะนำคือการเปิดใช้งานทั้งการติดตามและ stacktraces เพื่อค้นหาการรั่วไหลของงานและเพื่อปิดการใช้งานทั้งสองเมื่อเสร็จสิ้น
โดยค่าเริ่มต้น UnitAsk รองรับ TextMeshPro ( BindTo(TMP_Text)
และส่วน AsyncOperationHandle<T>
เหตุการณ์ TMP_InputField
เช่น UGUI InputField
มาตรฐาน), dotween ( Tween
ที่รอคอย) และที่อยู่ได้ ( AsyncOperationHandle
มีการกำหนดไว้ใน ASMDefs ที่คั่นด้วย UniTask.TextMeshPro
, UniTask.DOTween
, UniTask.Addressables
การสนับสนุน TextMeshPro และที่อยู่ได้จะเปิดใช้งานโดยอัตโนมัติเมื่อนำเข้าแพ็คเกจจาก Package Manager อย่างไรก็ตามสำหรับการสนับสนุน dotween หลังจากนำเข้าจากสินทรัพย์ระหว่าง dotween และกำหนดสคริปต์กำหนดสัญลักษณ์ UNITASK_DOTWEEN_SUPPORT
เพื่อเปิดใช้งาน
// sequential
await transform . DOMoveX ( 2 , 10 ) ;
await transform . DOMoveZ ( 5 , 20 ) ;
// parallel with cancellation
var ct = this . GetCancellationTokenOnDestroy ( ) ;
await UniTask . WhenAll (
transform . DOMoveX ( 10 , 3 ) . WithCancellation ( ct ) ,
transform . DOScale ( 10 , 3 ) . WithCancellation ( ct ) ) ;
พฤติกรรมเริ่มต้นของ Dotween Support ( await
WithCancellation
, ToUniTask
) รอ Tween ถูกฆ่าตาย มันทำงานได้ทั้งที่สมบูรณ์ (จริง/เท็จ) และฆ่า (จริง/เท็จ) แต่ถ้าคุณต้องการนำ Tweens กลับมาใช้ใหม่ ( SetAutoKill(false)
) มันไม่ได้ผลตามที่คาดไว้ หากคุณต้องการรอเวลาอื่นวิธีการขยายต่อไปนี้มีอยู่ใน Tween, AwaitForComplete
, AwaitForPause
, AwaitForPlay
, AwaitForRewind
, AwaitForStepComplete
Unity 2020.2 รองรับ C# 8.0 เพื่อให้คุณสามารถใช้ await foreach
นี่คือสัญลักษณ์การอัปเดตใหม่ในยุค Async
// Unity 2020.2, C# 8.0
await foreach ( var _ in UniTaskAsyncEnumerable . EveryUpdate ( ) . WithCancellation ( token ) )
{
Debug . Log ( " Update() " + Time . frameCount ) ;
}
ในสภาพแวดล้อม C# 7.3 คุณสามารถใช้วิธี ForEachAsync
เพื่อทำงานในลักษณะเดียวกัน
// C# 7.3(Unity 2018.3~)
await UniTaskAsyncEnumerable . EveryUpdate ( ) . ForEachAsync ( _ =>
{
Debug . Log ( " Update() " + Time . frameCount ) ;
} , token ) ;
UniTask.WhenEach
ที่คล้ายกับ Task.WhenEach
ของ. NET 9 เมื่อสามารถใช้วิธีใหม่สำหรับการรองานหลายงาน
await foreach ( var result in UniTask . WhenEach ( task1 , task2 , task3 ) )
{
// The result is of type WhenEachResult<T>.
// It contains either `T Result` or `Exception Exception`.
// You can check `IsCompletedSuccessfully` or `IsFaulted` to determine whether to access `.Result` or `.Exception`.
// If you want to throw an exception when `IsFaulted` and retrieve the result when successful, use `GetResult()`.
Debug . Log ( result . GetResult ( ) ) ;
}
untaskasyncenumerable asynchronous linq คล้ายกับ LINQ ใน IEnumerable<T>
หรือ rx ใน IObservable<T>
ผู้ให้บริการคิวรี LINQ มาตรฐานทั้งหมดสามารถนำไปใช้กับสตรีมแบบอะซิงโครนัสได้ ตัวอย่างเช่นรหัสต่อไปนี้แสดงวิธีการใช้ตัวกรองที่คลิกปุ่มคลิกแบบอะซิงโครนัสที่ทำงานได้ทุกครั้งที่มีการคลิกทุกครั้ง
await okButton . OnClickAsAsyncEnumerable ( ) . Where ( ( x , i ) => i % 2 == 0 ) . ForEachAsync ( _ =>
{
} ) ;
Fire and Forget Style (ตัวอย่างเช่นการจัดการเหตุการณ์) คุณสามารถใช้ Subscribe
okButton . OnClickAsAsyncEnumerable ( ) . Where ( ( x , i ) => i % 2 == 0 ) . Subscribe ( _ =>
{
} ) ;
async linq ถูกเปิดใช้งานเมื่อ using Cysharp.Threading.Tasks.Linq;
และ UniTaskAsyncEnumerable
ถูกกำหนดไว้ใน UniTask.Linq
asmdef
มันอยู่ใกล้กับ UNIRX (ส่วนขยายปฏิกิริยา) แต่ Unitaskasyncenumerable เป็นกระแสแบบอะซิงโครนัสแบบดึงในขณะที่ RX เป็นกระแสแบบอะซิงโครนัสแบบกด โปรดทราบว่าแม้ว่าจะคล้ายกัน แต่ลักษณะที่แตกต่างกันและรายละเอียดมีพฤติกรรมแตกต่างกันไปตามพวกเขา
UniTaskAsyncEnumerable
เป็นจุดเริ่มต้นเช่น Enumerable
นอกเหนือจากตัวดำเนินการสืบค้นมาตรฐานแล้วยังมีเครื่องกำเนิดไฟฟ้าอื่น ๆ สำหรับความสามัคคีเช่น EveryUpdate
, Timer
, TimerFrame
, Interval
, IntervalFrame
และ EveryValueChanged
และยัง TakeUntil
ผู้ให้บริการแบบสอบถามต้นฉบับ Unitask เพิ่มเติมเช่น Append
, Prepend
, DistinctUntilChanged
, ToHashSet
, Buffer
, CombineLatest
, Merge
Do
, Never
Subscribe
ForEachAsync
TakeUntilCanceled
Pairwise
, Publish
, Queue
, Return
, SkipUntil
, SkipUntilCanceled
, TakeLast
วิธีการที่มี Func เป็นอาร์กิวเมนต์มีการโอเวอร์โหลดเพิ่มเติมอีกสามครั้ง ***Await
***AwaitWithCancellation
Select ( Func < T , TR > selector )
SelectAwait ( Func < T , UniTask < TR > > selector )
SelectAwaitWithCancellation ( Func < T , CancellationToken , UniTask < TR > > selector )
หากคุณต้องการใช้วิธี async
ภายใน Func ให้ใช้ ***Await
หรือ ***AwaitWithCancellation
วิธีการสร้าง Async Iterator: C# 8.0 รองรับ Async Iterator ( async yield return
) แต่จะอนุญาตให้ IAsyncEnumerable<T>
และแน่นอนว่าต้องใช้ C# 8.0 Unitask รองรับ UniTaskAsyncEnumerable.Create
วิธีการสร้างตัววนซ้ำ ASYNC แบบกำหนดเอง
// IAsyncEnumerable, C# 8.0 version of async iterator. ( do not use this style, IAsyncEnumerable is not controled in UniTask).
public async IAsyncEnumerable < int > MyEveryUpdate ( [ EnumeratorCancellation ] CancellationToken cancelationToken = default )
{
var frameCount = 0 ;
await UniTask . Yield ( ) ;
while ( ! token . IsCancellationRequested )
{
yield return frameCount ++ ;
await UniTask . Yield ( ) ;
}
}
// UniTaskAsyncEnumerable.Create and use `await writer.YieldAsync` instead of `yield return`.
public IUniTaskAsyncEnumerable < int > MyEveryUpdate ( )
{
// writer(IAsyncWriter<T>) has `YieldAsync(value)` method.
return UniTaskAsyncEnumerable . Create < int > ( async ( writer , token ) =>
{
var frameCount = 0 ;
await UniTask . Yield ( ) ;
while ( ! token . IsCancellationRequested )
{
await writer . YieldAsync ( frameCount ++ ) ; // instead of `yield return`
await UniTask . Yield ( ) ;
}
} ) ;
}
ส่วนประกอบ UGUI ทั้งหมดใช้ ***AsAsyncEnumerable
เพื่อแปลงกระแสอะซิงโครนัสของเหตุการณ์
async UniTask TripleClick ( )
{
// In default, used button.GetCancellationTokenOnDestroy to manage lieftime of async
await button . OnClickAsync ( ) ;
await button . OnClickAsync ( ) ;
await button . OnClickAsync ( ) ;
Debug . Log ( " Three times clicked " ) ;
}
// more efficient way
async UniTask TripleClick ( )
{
using ( var handler = button . GetAsyncClickEventHandler ( ) )
{
await handler . OnClickAsync ( ) ;
await handler . OnClickAsync ( ) ;
await handler . OnClickAsync ( ) ;
Debug . Log ( " Three times clicked " ) ;
}
}
// use async LINQ
async UniTask TripleClick ( CancellationToken token )
{
await button . OnClickAsAsyncEnumerable ( ) . Take ( 3 ) . Last ( ) ;
Debug . Log ( " Three times clicked " ) ;
}
// use async LINQ2
async UniTask TripleClick ( CancellationToken token )
{
await button . OnClickAsAsyncEnumerable ( ) . Take ( 3 ) . ForEachAsync ( _ =>
{
Debug . Log ( " Every clicked " ) ;
} ) ;
Debug . Log ( " Three times clicked, complete. " ) ;
}
เหตุการณ์ข้อความ monobehaviour ทั้งหมดสามารถแปลง async-streams โดย AsyncTriggers
ที่สามารถเปิดใช้งานได้โดย using Cysharp.Threading.Tasks.Triggers;
- Asynctrigger สามารถสร้างขึ้นได้โดยใช้ GetAsync***Trigger
และทริกเกอร์ตัวเองเป็น untaskasyncenumerable
var trigger = this . GetOnCollisionEnterAsyncHandler ( ) ;
await trigger . OnCollisionEnterAsync ( ) ;
await trigger . OnCollisionEnterAsync ( ) ;
await trigger . OnCollisionEnterAsync ( ) ;
// every moves.
await this . GetAsyncMoveTrigger ( ) . ForEachAsync ( axisEventData =>
{
} ) ;
AsyncReactiveProperty
, AsyncReadOnlyReactiveProperty
เป็น ReactiveProperty เวอร์ชันของ UnitASK วิธีการขยาย BindTo
ของ IUniTaskAsyncEnumerable<T>
สำหรับการเชื่อมโยงค่าสตรีมแบบอะซิงโครนัสกับส่วนประกอบ Unity (text/selectable/tmp/text)
var rp = new AsyncReactiveProperty < int > ( 99 ) ;
// AsyncReactiveProperty itself is IUniTaskAsyncEnumerable, you can query by LINQ
rp . ForEachAsync ( x =>
{
Debug . Log ( x ) ;
} , this . GetCancellationTokenOnDestroy ( ) ) . Forget ( ) ;
rp . Value = 10 ; // push 10 to all subscriber
rp . Value = 11 ; // push 11 to all subscriber
// WithoutCurrent ignore initial value
// BindTo bind stream value to unity components.
rp . WithoutCurrent ( ) . BindTo ( this . textComponent ) ;
await rp . WaitAsync ( ) ; // wait until next value set
// also exists ToReadOnlyAsyncReactiveProperty
var rp2 = new AsyncReactiveProperty < int > ( 99 ) ;
var rorp = rp . CombineLatest ( rp2 , ( x , y ) => ( x , y ) ) . ToReadOnlyAsyncReactiveProperty ( CancellationToken . None ) ;
สตรีมแบบอะซิงโครนัสแบบดึงไม่ได้รับค่าถัดไปจนกว่าการประมวลผลแบบอะซิงโครนัสในลำดับจะเสร็จสมบูรณ์ สิ่งนี้สามารถหกข้อมูลจากเหตุการณ์ประเภทพุชเช่นปุ่ม
// can not get click event during 3 seconds complete.
await button . OnClickAsAsyncEnumerable ( ) . ForEachAwaitAsync ( async x =>
{
await UniTask . Delay ( TimeSpan . FromSeconds ( 3 ) ) ;
} ) ;
มันมีประโยชน์ (ป้องกันการดับเบิลคลิก) แต่ไม่มีประโยชน์บางครั้ง
การใช้วิธี Queue()
จะคิวเหตุการณ์ในระหว่างการประมวลผลแบบอะซิงโครนัส
// queued message in asynchronous processing
await button . OnClickAsAsyncEnumerable ( ) . Queue ( ) . ForEachAwaitAsync ( async x =>
{
await UniTask . Delay ( TimeSpan . FromSeconds ( 3 ) ) ;
} ) ;
หรือใช้ Subscribe
ไฟและลืมสไตล์
button . OnClickAsAsyncEnumerable ( ) . Subscribe ( async x =>
{
await UniTask . Delay ( TimeSpan . FromSeconds ( 3 ) ) ;
} ) ;
Channel
เหมือนกับ System.threading.tasks.channels ซึ่งคล้ายกับช่อง Golang
ขณะนี้รองรับช่องทางที่ไม่ได้รับการศึกษาหลายคนเท่านั้น มันสามารถสร้างโดย Channel.CreateSingleConsumerUnbounded<T>()
สำหรับผู้ผลิต ( .Writer
) ให้ใช้ TryWrite
เพื่อผลักดันค่าและ TryComplete
เพื่อทำช่องให้สมบูรณ์ สำหรับผู้บริโภค ( .Reader
) ใช้ TryRead
, WaitToReadAsync
, ReadAsync
, Completion
และ ReadAllAsync
เพื่ออ่านข้อความที่รอคิว
ReadAllAsync
ส่งคืน IUniTaskAsyncEnumerable<T>
ดังนั้นผู้ดำเนินการค้นหา LINQ ผู้อ่านอนุญาตให้ผู้บริโภคคนเดียวเท่านั้น แต่ใช้ .Publish()
ตัวดำเนินการสืบค้นเพื่อเปิดใช้งานข้อความมัลติคาสต์ ตัวอย่างเช่นทำยูทิลิตี้ผับ/ย่อย
public class AsyncMessageBroker < T > : IDisposable
{
Channel < T > channel ;
IConnectableUniTaskAsyncEnumerable < T > multicastSource ;
IDisposable connection ;
public AsyncMessageBroker ( )
{
channel = Channel . CreateSingleConsumerUnbounded < T > ( ) ;
multicastSource = channel . Reader . ReadAllAsync ( ) . Publish ( ) ;
connection = multicastSource . Connect ( ) ; // Publish returns IConnectableUniTaskAsyncEnumerable.
}
public void Publish ( T value )
{
channel . Writer . TryWrite ( value ) ;
}
public IUniTaskAsyncEnumerable < T > Subscribe ( )
{
return multicastSource ;
}
public void Dispose ( )
{
channel . Writer . TryComplete ( ) ;
connection . Dispose ( ) ;
}
}
Unity 6 แนะนำประเภทที่รอคอยได้รอคอย เพื่อให้ง่ายการรอคอยสามารถพิจารณาได้ว่าเป็นชุดย่อยของ UnitASK และในความเป็นจริงการออกแบบของ Areaible ได้รับอิทธิพลจาก UnitASK มันควรจะสามารถจัดการกับการรอคอยที่ใช้ PlayerLoop งานที่รวบรวมและสนับสนุนการยกเลิกด้วย CancellationToken
ในลักษณะเดียวกัน ด้วยการรวมไว้ในไลบรารีมาตรฐานคุณอาจสงสัยว่าจะใช้ Unitask หรืออพยพไปยังที่รอคอยหรือไม่ นี่คือคำแนะนำสั้น ๆ
ก่อนอื่นฟังก์ชั่นที่ได้รับจากการรอคอยนั้นเทียบเท่ากับสิ่งที่ Coroutines เสนอ แทนที่จะ yield return
คุณใช้รอ await NextFrameAsync()
แทนที่ yield return null
; และมีความเท่าเทียมกันสำหรับ WaitForSeconds
และ EndOfFrame
อย่างไรก็ตามนั่นคือขอบเขตของมัน เป็นพื้นฐานของการทำงานของ Coroutine มันขาดคุณสมบัติตามงาน ในการพัฒนาแอพพลิเคชั่นที่ใช้งานได้จริงโดยใช้ Async/รอการดำเนินการเช่น WhenAll
เป็นสิ่งจำเป็น นอกจากนี้ UnitASK ยังเปิดใช้งานการดำเนินการตามเฟรมจำนวนมาก (เช่น DelayFrame
) และการควบคุม playerlooptiming ที่ยืดหยุ่นมากขึ้นซึ่งไม่สามารถรอได้ แน่นอนว่าไม่มีหน้าต่างตัวติดตามเช่นกัน
ดังนั้นฉันขอแนะนำให้ใช้ UNTASK สำหรับการพัฒนาแอปพลิเคชัน Unitask เป็นชุดที่รอคอยและมีคุณสมบัติที่สำคัญมากมาย สำหรับการพัฒนาห้องสมุดที่คุณต้องการหลีกเลี่ยงการพึ่งพาภายนอกโดยใช้การรอคอยเป็นประเภทการส่งคืนสำหรับวิธีการจะเหมาะสม รอได้สามารถแปลงเป็น UnitASK โดยใช้ AsUniTask
ดังนั้นจึงไม่มีปัญหาในการจัดการฟังก์ชั่นการใช้งานที่รอคอยภายในห้องสมุด UnitASK แน่นอนถ้าคุณไม่จำเป็นต้องกังวลเกี่ยวกับการพึ่งพาการใช้ UnitAsk จะเป็นตัวเลือกที่ดีที่สุดแม้สำหรับการพัฒนาห้องสมุด
แอตทริบิวต์ [UnityTest]
ของ Unity สามารถทดสอบ coroutine (ienumerator) แต่ไม่สามารถทดสอบ async ได้ UniTask.ToCoroutine
Bridges Async/รอ Coroutine เพื่อให้คุณสามารถทดสอบวิธีการ async
[ UnityTest ]
public IEnumerator DelayIgnore ( ) => UniTask . ToCoroutine ( async ( ) =>
{
var time = Time . realtimeSinceStartup ;
Time . timeScale = 0.5f ;
try
{
await UniTask . Delay ( TimeSpan . FromSeconds ( 3 ) , ignoreTimeScale : true ) ;
var elapsed = Time . realtimeSinceStartup - time ;
Assert . AreEqual ( 3 , ( int ) Math . Round ( TimeSpan . FromSeconds ( elapsed ) . TotalSeconds , MidpointRounding . ToEven ) ) ;
}
finally
{
Time . timeScale = 1.0f ;
}
} ) ;
การทดสอบหน่วยของ Unitask นั้นเขียนขึ้นโดยใช้ Unity Test Runner และ Cysharp/RuntimeUnittestToolkit เพื่อรวมเข้ากับ CI และตรวจสอบว่า IL2CPP ใช้งานได้หรือไม่
วิธีการ UNETASK ส่วนใหญ่ทำงานบนเธรดเดียว (PlayerLoop) โดยมีเพียง UniTask.Run
( Task.Run
เทียบเท่า) และ UniTask.SwitchToThreadPool
ทำงานบนพูลเธรด หากคุณใช้พูลเธรดมันจะไม่ทำงานกับ WebGL และอื่น ๆ
UniTask.Run
ตอนนี้เลิกใช้แล้ว คุณสามารถใช้ UniTask.RunOnThreadPool
แทน และลองพิจารณาว่าคุณสามารถใช้ UniTask.Create
หรือ UniTask.Void
คุณสามารถแปลง coroutine (ienumerator) เป็น unitask (หรือรอโดยตรง) แต่มีข้อ จำกัด บางอย่าง
WaitForEndOfFrame
/ WaitForFixedUpdate
/ Coroutine
StartCoroutine
มันใช้ PlayerLoopTiming
ที่ระบุและเครื่องเล่นเริ่มต้นที่เริ่มต้น PlayerLoopTiming.Update
จะทำงานก่อน Update
ของ Monobehaviour และ Loop ของ StartCoroutine
หากคุณต้องการการแปลงที่เข้ากันได้อย่างสมบูรณ์จาก coroutine เป็น async ให้ใช้ IEnumerator.ToUniTask(MonoBehaviour coroutineRunner)
โอเวอร์โหลด มันดำเนินการ startCoroutine ในอินสแตนซ์ของอาร์กิวเมนต์ monobehaviour และรอให้มันเสร็จสมบูรณ์ใน UnitASK
Unitask สามารถทำงานบน Unity Editor เช่น Coroutine บรรณาธิการ อย่างไรก็ตามมีข้อ จำกัด บางประการ
DelayType.Realtime
ที่รอเวลาที่เหมาะสมEditorApplication.update
Timing.update-batchmode
ด้วย -quit
ไม่ทำงานเพราะ Unity ไม่ได้เรียกใช้ EditorApplication.update
และออกจากเฟรมเดียว อย่าใช้ -quit
และออกจากด้วยตนเองด้วย EditorApplication.Exit(0)
UnitASK มี API แบบมาตรฐานหลายอย่าง ตารางนี้แสดงให้เห็นว่า API ทางเลือกคืออะไร
ใช้ประเภทมาตรฐาน
ประเภท. NET | ประเภท Unitask |
---|---|
IProgress<T> | - |
CancellationToken | - |
CancellationTokenSource | - |
ใช้ประเภท Unitask
ประเภท. NET | ประเภท Unitask |
---|---|
Task / ValueTask | UniTask |
Task<T> / ValueTask<T> | UniTask<T> |
async void | async UniTaskVoid |
+= async () => { } | UniTask.Void , UniTask.Action , UniTask.UnityAction |
- | UniTaskCompletionSource |
TaskCompletionSource<T> | UniTaskCompletionSource<T> / AutoResetUniTaskCompletionSource<T> |
ManualResetValueTaskSourceCore<T> | UniTaskCompletionSourceCore<T> |
IValueTaskSource | IUniTaskSource |
IValueTaskSource<T> | IUniTaskSource<T> |
ValueTask.IsCompleted | UniTask.Status.IsCompleted() |
ValueTask<T>.IsCompleted | UniTask<T>.Status.IsCompleted() |
new Progress<T> | Progress.Create<T> |
CancellationToken.Register(UnsafeRegister) | CancellationToken.RegisterWithoutCaptureExecutionContext |
CancellationTokenSource.CancelAfter | CancellationTokenSource.CancelAfterSlim |
Channel.CreateUnbounded<T>(false){ SingleReader = true } | Channel.CreateSingleConsumerUnbounded<T> |
IAsyncEnumerable<T> | IUniTaskAsyncEnumerable<T> |
IAsyncEnumerator<T> | IUniTaskAsyncEnumerator<T> |
IAsyncDisposable | IUniTaskAsyncDisposable |
Task.Delay | UniTask.Delay |
Task.Yield | UniTask.Yield |
Task.Run | UniTask.RunOnThreadPool |
Task.WhenAll | UniTask.WhenAll |
Task.WhenAny | UniTask.WhenAny |
Task.WhenEach | UniTask.WhenEach |
Task.CompletedTask | UniTask.CompletedTask |
Task.FromException | UniTask.FromException |
Task.FromResult | UniTask.FromResult |
Task.FromCanceled | UniTask.FromCanceled |
Task.ContinueWith | UniTask.ContinueWith |
TaskScheduler.UnobservedTaskException | UniTaskScheduler.UnobservedTaskException |
Unitask แคช Async ให้คำมั่นสัญญาอย่างจริงจังเพื่อให้ได้การจัดสรรเป็นศูนย์ (สำหรับรายละเอียดทางเทคนิคดูบล็อกโพสต์ Unitask V2 - ศูนย์การจัดสรร async/รอคอยความสามัคคีด้วย LINQ แบบอะซิงโครนัส) โดยค่าเริ่มต้นมันแคชสัญญาทั้งหมด แต่คุณสามารถกำหนดค่า TaskPool.SetMaxPoolSize
ตามค่าของคุณค่าระบุขนาดแคชต่อประเภท TaskPool.GetCacheSizeInfo
ส่งคืนวัตถุที่แคชในปัจจุบันในสระว่ายน้ำ
foreach ( var ( type , size ) in TaskPool . GetCacheSizeInfo ( ) )
{
Debug . Log ( type + " : " + size ) ;
}
ใน UnityEditor Profiler แสดงการจัดสรรคอมไพเลอร์ที่สร้าง asyncstatemachine แต่มันเกิดขึ้นในการดีบัก (การพัฒนา) สร้างเท่านั้น C# Compiler สร้าง AsyncStateMachine เป็นคลาสบน Debug Build และ As Struct on Release Build
Unity รองรับตัวเลือกการเพิ่มประสิทธิภาพรหัสเริ่มต้นในปี 2020.1 (ขวา, ส่วนท้าย)
คุณสามารถเปลี่ยนการเพิ่มประสิทธิภาพคอมไพเลอร์ C# เพื่อปลดปล่อยเพื่อลบการจัดสรร asyncstatemachine ในการพัฒนาการสร้าง ตัวเลือกการเพิ่มประสิทธิภาพนี้ยังสามารถตั้งค่าผ่าน Compilation.CompilationPipeline-codeOptimization
และ Compilation.CodeOptimization
SynchronizationContext เริ่มต้นของ Unity ( UnitySynchronizationContext
) เป็นการใช้งานที่ไม่ดีสำหรับประสิทธิภาพ UnitASK บายพาสซิง SynchronizationContext
(และ ExecutionContext
) ดังนั้นจึงไม่ได้ใช้ แต่ถ้ามีอยู่ใน async Task
ยังคงใช้มันอยู่ UniTaskSynchronizationContext
เป็นการแทนที่ UnitySynchronizationContext
ซึ่งดีกว่าสำหรับประสิทธิภาพ
public class SyncContextInjecter
{
[ RuntimeInitializeOnLoadMethod ( RuntimeInitializeLoadType . SubsystemRegistration ) ]
public static void Inject ( )
{
SynchronizationContext . SetSynchronizationContext ( new UniTaskSynchronizationContext ( ) ) ;
}
}
นี่เป็นตัวเลือกที่เป็นทางเลือกและไม่แนะนำเสมอไป UniTaskSynchronizationContext
มีประสิทธิภาพน้อยกว่า async UniTask
และไม่ใช่การเปลี่ยน UnitASK ที่สมบูรณ์ นอกจากนี้ยังไม่รับประกันความเข้ากันได้ของพฤติกรรมอย่างเต็มรูปแบบกับ UnitySynchronizationContext
การอ้างอิง API ของ Unitask นั้นโฮสต์ที่ cysharp.github.io/unitask โดย DOCFX และ Cysharp/DocFxtemplate
ตัวอย่างเช่นวิธีการโรงงานของ UnitAsk สามารถดูได้ที่ UNITASK#วิธี วิธีการขยายโรงงาน/ส่วนขยายของ UnitaskasyncEnumerable สามารถดูได้ด้วยวิธี#วิธี#
ต้องการเวอร์ชันของความสามัคคีที่รองรับพารามิเตอร์การสืบค้นเส้นทางสำหรับแพ็คเกจ GIT (Unity> = 2019.3.4F1, Unity> = 2020.1A21) คุณสามารถเพิ่ม https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask
ไปยัง Package Manager
หรือเพิ่ม "com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"
ไปยัง Packages/manifest.json
หากคุณต้องการตั้งค่าเวอร์ชันเป้าหมาย UNITASK ใช้ *.*.*
แท็กรีลีสเพื่อให้คุณสามารถระบุเวอร์ชันเช่น #2.1.0
ตัวอย่างเช่น https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.1.0
สำหรับ. NET Core ให้ใช้ NUGET
PM> Install-Package UnitAsk
Unitask ของ. NET Core เวอร์ชันเป็นชุดย่อยของ Unity UnitAsk พร้อมวิธีการขึ้นอยู่กับ PlayerLoop
มันทำงานที่ประสิทธิภาพที่สูงกว่างานมาตรฐาน/Valuetask แต่คุณควรระมัดระวังในการละเว้น ExecutionContext/SynchronizationContext เมื่อใช้งาน AsyncLocal
ยังไม่ทำงานเพราะจะไม่สนใจ ExecutionContext
หากคุณใช้ Unitask ภายใน แต่ให้ Valuetask เป็น API ภายนอกคุณสามารถเขียนได้เช่นต่อไปนี้ (ได้รับแรงบันดาลใจจาก Pooledawait)
public class ZeroAllocAsyncAwaitInDotNetCore
{
public ValueTask < int > DoAsync ( int x , int y )
{
return Core ( this , x , y ) ;
static async UniTask < int > Core ( ZeroAllocAsyncAwaitInDotNetCore self , int x , int y )
{
// do anything...
await Task . Delay ( TimeSpan . FromSeconds ( x + y ) ) ;
await UniTask . Yield ( ) ;
return 10 ;
}
}
}
// UniTask does not return to original SynchronizationContext but you can use helper `ReturnToCurrentSynchronizationContext`.
public ValueTask TestAsync ( )
{
await using ( UniTask . ReturnToCurrentSynchronizationContext ( ) )
{
await UniTask . SwitchToThreadPool ( ) ;
// do anything..
}
}
.NET Core เวอร์ชันมีวัตถุประสงค์เพื่อให้ผู้ใช้ใช้ UnitASK เป็นอินเทอร์เฟซเมื่อแชร์รหัสกับ Unity (เช่น Cysharp/Magiconion) .NET Core เวอร์ชันของ UnitAsk เปิดใช้งานการแชร์รหัสที่ราบรื่น
วิธีการยูทิลิตี้เช่นเมื่อทั้งหมดซึ่งเทียบเท่ากับ UnitASK นั้นมีให้เป็น CYSHARP/VALUETASKSUPPLEMENT
ห้องสมุดนี้อยู่ภายใต้ใบอนุญาต MIT