Compare commits

...

53 Commits

Author SHA1 Message Date
dependabot[bot]
46c690e19a chore(deps): bump 1password/load-secrets-action
Bumps the dependencies group with 1 update: [1password/load-secrets-action](https://github.com/1password/load-secrets-action).


Updates `1password/load-secrets-action` from 2.0.0 to 4.0.0
- [Release notes](https://github.com/1password/load-secrets-action/releases)
- [Commits](581a835fb5...92467eb28f)

---
updated-dependencies:
- dependency-name: 1password/load-secrets-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-13 12:28:26 +00:00
Ikiru Yoshizaki
a9e27c03d4 chore: add groups for dependabot 2026-02-25 18:03:24 +09:00
Ikiru Yoshizaki
73a63b7f67 Merge pull request #680 from Cysharp/ci/nuget_release
ci: add id-token: write for NuGet Trusted Publish
2025-10-01 16:31:33 +09:00
Ikiru Yoshizaki
30bec5d5c4 ci: add id-token: write for NuGet Trusted Publish 2025-10-01 16:03:54 +09:00
Ikiru Yoshizaki
ec9204d381 Merge pull request #679 from Cysharp/feature/nuget
chore: Specify IsPackable=false on Directory.Build.props, explicitly true for target packages.
2025-09-25 01:12:10 +09:00
Ikiru Yoshizaki
9a6584ff0d chore: Specify IsPackable=false on Directory.Build.props, explicitly true for target packages. 2025-09-24 21:17:25 +09:00
Ikiru Yoshizaki
98538ef7c7 Merge pull request #677 from Cysharp/feature/docs
ci: replace docfx build from container to docfx command
2025-09-13 01:23:03 +09:00
Ikiru Yoshizaki
519590ca6e chore: exclude tests assemblies 2025-09-10 18:38:31 +09:00
Ikiru Yoshizaki
dce4366f3e ci: change docfx target from source code to Unity built assemblies 2025-09-10 18:20:17 +09:00
Ikiru Yoshizaki
88026ab14a ci: replace docfx build from container to docfx command 2025-09-10 17:20:43 +09:00
Ikiru Yoshizaki
95998ff3f2 ci: dependabot cooldown 65d2ae 2025-09-10 12:34:40 +09:00
Ikiru Yoshizaki
64285ae060 Merge pull request #672 from Cysharp/ci/nuget_readme
chore: add README.md to nuget package
2025-08-19 18:37:19 +09:00
Ikiru Yoshizaki
40e020fb02 ci: add dotnet pack and use Release 2025-08-19 17:42:54 +09:00
Ikiru Yoshizaki
e63dba1350 chore: add README.md to nuget package 2025-08-19 17:37:09 +09:00
Ikiru Yoshizaki
f213ff497e ci: missing permission 2025-05-14 15:41:23 +09:00
Ikiru Yoshizaki
7568061eda ci: fix ghalint 2025-05-14 12:39:03 +09:00
Ikiru Yoshizaki
459a572c1d chore: add .editorconfig 2025-05-14 12:38:53 +09:00
Ikiru Yoshizaki
06067cd4c8 ci: use Cysharp/Actions checkout instead of 3rd party directly 2025-03-19 15:44:16 +09:00
Ikiru Yoshizaki
d9983cfe27 Merge pull request #654 from Cysharp/feature/pin_action
ci: Pinning third party GitHub Actions sha
2025-03-18 17:33:39 +09:00
Ikiru Yoshizaki
70eb7cd3ee ci: Pinning third party GitHub Actions sha 2025-03-18 16:58:33 +09:00
Ikiru Yoshizaki
cc3c70af90 ci: update vault 2025-03-11 12:55:51 +09:00
Ikiru Yoshizaki
8042b29ff8 ci: extend timeout 2025-01-07 12:29:34 +09:00
Yoshifumi Kawai
b0d01ca75f Merge pull request #641 from hmkc/dev
Update README_CN.md
2024-12-12 16:35:32 +09:00
hmkc
7a63ab7088 Update README_CN.md
Re-translated the documentation.
2024-12-11 17:13:32 +08:00
Yoshifumi Kawai
bdf102f145 Merge pull request #639 from hmkc/dev
Update README_CN.md
2024-12-09 20:18:43 +09:00
hmkc
41cea030ab Update README_CN.md 2024-12-09 18:39:08 +08:00
Yoshifumi Kawai
f9fd769be7 Update README.md 2024-10-09 14:46:23 +09:00
neuecc
579304fe47 chore(docs): update TOC 2024-10-08 14:02:41 +00:00
Yoshifumi Kawai
740ca7ef01 Awaitable notes 2024-10-08 23:02:26 +09:00
github-actions[bot]
7c0f199fe0 feat: Update package.json to 2.5.10 2024-10-03 00:42:37 +00:00
neuecc
9a3ec31533 Merge remote-tracking branch 'origin/master' 2024-10-03 09:41:47 +09:00
neuecc
37d8f4f48e C# 8 #624 2024-10-03 09:41:30 +09:00
github-actions[bot]
27a0c06ede feat: Update package.json to 2.5.9 2024-10-01 06:16:52 +00:00
neuecc
e4082ecd75 Merge remote-tracking branch 'origin/master' 2024-10-01 14:59:02 +09:00
neuecc
3b0fd784ff meta 2024-10-01 14:58:57 +09:00
neuecc
dc9ebfd765 Add AsyncInstantiateOperation.WithCancellation, ToUniTask for Unity 2022.3.20/Unity 2022.3 or newer 2024-10-01 14:50:13 +09:00
Yoshifumi Kawai
005c83fbd7 Merge pull request #623 from Cysharp/feature/ci
ci: remove Unity 2021 LTS from Build matrix
2024-10-01 10:44:30 +09:00
github-actions[bot]
5984b67ecb feat: Update package.json to 2.5.8 2024-09-30 11:45:57 +00:00
neuecc
05fdf48058 Merge remote-tracking branch 'origin/master' 2024-09-30 20:43:06 +09:00
Yoshifumi Kawai
647ed6ff82 Merge pull request #621 from dvsilch/master
add generic type UnityAction support & fix typo
2024-09-30 20:42:57 +09:00
neuecc
0826b7e976 Add UniTask.WhenEach 2024-09-30 20:42:17 +09:00
dvsilch
a51632cd4b fix: add overloads for CancellationToken 2024-09-30 10:13:29 +08:00
dvsilch
bf945a7ef4 fix: rename parameters and type parameters 2024-09-30 10:11:36 +08:00
dvsilch
353f15e94f fix: add .Forget() call and rename paramters to keep coding style consistent 2024-09-27 23:45:05 +08:00
dvsilch
cf19f18662 fix: typo 2024-09-27 22:57:36 +08:00
dvsilch
fdb9d1cf95 feature: add overload in UniTask.UnityAction to support generic type UnityAction 2024-09-27 22:57:05 +08:00
Yoshifumi Kawai
2e0917428b Merge pull request #620 from albermotion/master
Added a note for users using Unity 2023.1 or newer to prevent compilation error
2024-09-27 09:48:04 +09:00
Alberto
0b16005f4b Added a note for users using Unity 2023.1 or newer 2024-09-26 22:33:37 +02:00
github-actions[bot]
74bbe87b58 feat: Update package.json to 2.5.7 2024-09-26 06:24:48 +00:00
Yoshifumi Kawai
6f4131539b Merge pull request #619 from kochounoyume/add-state-argument
Add overload in UniTask.WaitUntil, UniTask.WaitWhile and UniTask.Defer
2024-09-26 12:52:56 +09:00
Kochoyume
06283f0ffb Add to the code that I forgot to write 2024-09-24 23:58:28 +09:00
Kochoyume
dfe5ee43c2 Add overload in UniTask.WaitUntil, UniTask.WaitWhile and UniTask.Defer to avoid closure allocation 2024-09-24 23:37:44 +09:00
Ikiru Yoshizaki
eaa553dc83 ci: remove Unity 2021 LTS from Build matrix 2024-09-24 18:30:54 +09:00
35 changed files with 1746 additions and 387 deletions

13
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"docfx": {
"version": "2.78.3",
"commands": [
"docfx"
],
"rollForward": false
}
}
}

41
.editorconfig Normal file
View File

@@ -0,0 +1,41 @@
# top-most EditorConfig file
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
# Visual Studio Spell checker configs (https://learn.microsoft.com/en-us/visualstudio/ide/text-spell-checker?view=vs-2022#how-to-customize-the-spell-checker)
spelling_exclusion_path = ./exclusion.dic
[*.cs]
indent_size = 4
charset = utf-8-bom
end_of_line = unset
# Solution files
[*.{sln,slnx}]
end_of_line = unset
# MSBuild project files
[*.{csproj,props,targets}]
end_of_line = unset
# Xml config files
[*.{ruleset,config,nuspec,resx,runsettings,DotSettings}]
end_of_line = unset
[*{_AssemblyInfo.cs,.notsupported.cs}]
generated_code = true
# C# code style settings
[*.{cs}]
dotnet_diagnostic.IDE0044.severity = none # IDE0044: Make field readonly
# https://stackoverflow.com/questions/79195382/how-to-disable-fading-unused-methods-in-visual-studio-2022-17-12-0
dotnet_diagnostic.IDE0051.severity = none # IDE0051: Remove unused private member
dotnet_diagnostic.IDE0130.severity = none # IDE0130: Namespace does not match folder structure

View File

@@ -5,3 +5,14 @@ updates:
directory: "/"
schedule:
interval: "weekly" # Check for updates to GitHub Actions every week
groups:
dependencies:
patterns:
- "*"
cooldown:
default-days: 14 # Wait 14 days before creating another PR for the same dependency. This will prevent vulnerability on the package impact.
ignore:
# I just want update action when major/minor version is updated. patch updates are too noisy.
- dependency-name: "*"
update-types:
- version-update:semver-patch

View File

@@ -10,13 +10,16 @@ on:
jobs:
build-dotnet:
runs-on: ubuntu-latest
permissions:
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: Cysharp/Actions/.github/actions/checkout@main
- uses: Cysharp/Actions/.github/actions/setup-dotnet@main
- run: dotnet build -c Debug
- run: dotnet test -c Debug
- run: dotnet build -c Release
- run: dotnet test -c Release
- run: dotnet pack -c Release --no-build -p:IncludeSymbols=true -o $GITHUB_WORKSPACE/artifacts
build-unity:
if: ${{ ((github.event_name == 'push' && github.repository_owner == 'Cysharp') || startsWith(github.event.pull_request.head.label, 'Cysharp:')) && github.triggering_actor != 'dependabot[bot]' }}
@@ -24,27 +27,29 @@ jobs:
fail-fast: false
max-parallel: 2
matrix:
unity: ["2021.3.41f1", "2022.3.39f1", "6000.0.12f1"] # Test with LTS
runs-on: ubuntu-latest
timeout-minutes: 20
unity: ["2022.3.39f1", "6000.0.12f1"] # Test with LTS
permissions:
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 30 # Unity build takes more than 20min.
steps:
- name: Load secrets
id: op-load-secret
uses: 1password/load-secrets-action@v2
uses: 1password/load-secrets-action@92467eb28f72e8255933372f1e0707c567ce2259 # v4.0.0
with:
export-env: false
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN_PUBLIC }}
UNITY_EMAIL: "op://GitHubActionsPublic/UNITY_LICENSE/username"
UNITY_PASSWORD: "op://GitHubActionsPublic/UNITY_LICENSE/credential"
UNITY_SERIAL: "op://GitHubActionsPublic/UNITY_LICENSE/serial"
UNITY_EMAIL: "op://${{ vars.OP_VAULT_ACTIONS_PUBLIC }}/UNITY_LICENSE/username"
UNITY_PASSWORD: "op://${{ vars.OP_VAULT_ACTIONS_PUBLIC }}/UNITY_LICENSE/credential"
UNITY_SERIAL: "op://${{ vars.OP_VAULT_ACTIONS_PUBLIC }}/UNITY_LICENSE/serial"
- uses: actions/checkout@v4
- uses: Cysharp/Actions/.github/actions/checkout@main
# Execute scripts: Export Package
# /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod PackageExporter.Export
- name: Build Unity (.unitypacakge)
if: ${{ startsWith(matrix.unity, '2021') }} # only execute once
if: ${{ startsWith(matrix.unity, '2022') }} # only execute once
uses: Cysharp/Actions/.github/actions/unity-builder@main
env:
UNITY_EMAIL: ${{ steps.op-load-secret.outputs.UNITY_EMAIL }}
@@ -56,10 +61,6 @@ jobs:
targetPlatform: StandaloneLinux64
buildMethod: PackageExporter.Export
- uses: Cysharp/Actions/.github/actions/check-metas@main # check meta files
with:
directory: src/UniTask
# Execute UnitTest
# /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod UnitTestBuilder.BuildUnitTest /headless /ScriptBackend IL2CPP /BuildTarget StandaloneLinux64
- name: Build UnitTest (IL2CPP)
@@ -79,6 +80,10 @@ jobs:
- name: Execute UnitTest
run: ./src/UniTask/bin/UnitTest/StandaloneLinux64_IL2CPP/test
- uses: Cysharp/Actions/.github/actions/check-metas@main # check meta files
with:
directory: src/UniTask
# Store artifacts.
- uses: Cysharp/Actions/.github/actions/upload-artifact@main
if: ${{ startsWith(matrix.unity, '2021') }} # only execute 2021

58
.github/workflows/build-docs.yaml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: build-docs
on:
push:
branches:
- master
- feature/docs
jobs:
run-docfx:
if: ${{ ((github.event_name == 'push' && github.repository_owner == 'Cysharp') || startsWith(github.event.pull_request.head.label, 'Cysharp:')) && github.triggering_actor != 'dependabot[bot]' }}
permissions:
contents: write
pages: write
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Load secrets
id: op-load-secret
uses: 1password/load-secrets-action@92467eb28f72e8255933372f1e0707c567ce2259 # v4.0.0
with:
export-env: false
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN_PUBLIC }}
UNITY_EMAIL: "op://${{ vars.OP_VAULT_ACTIONS_PUBLIC }}/UNITY_LICENSE/username"
UNITY_PASSWORD: "op://${{ vars.OP_VAULT_ACTIONS_PUBLIC }}/UNITY_LICENSE/credential"
UNITY_SERIAL: "op://${{ vars.OP_VAULT_ACTIONS_PUBLIC }}/UNITY_LICENSE/serial"
- uses: Cysharp/Actions/.github/actions/checkout@main
# Execute scripts: Export Package
# /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod PackageExporter.Export
- name: Build Unity (.unitypackage)
uses: Cysharp/Actions/.github/actions/unity-builder@main
env:
UNITY_EMAIL: ${{ steps.op-load-secret.outputs.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ steps.op-load-secret.outputs.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ steps.op-load-secret.outputs.UNITY_SERIAL }}
with:
projectPath: src/UniTask
unityVersion: "2022.3.39f1"
targetPlatform: StandaloneLinux64
buildMethod: PackageExporter.Export
- uses: Cysharp/Actions/.github/actions/checkout@main
with:
repository: Cysharp/DocfxTemplate
path: docs/_DocfxTemplate
- uses: Cysharp/Actions/.github/actions/setup-dotnet@main
- name: dotnet tool restore
run: dotnet tool restore
- name: Docfx metadata
run: dotnet docfx metadata docs/docfx.json
- name: Docfx build
run: dotnet docfx build docs/docfx.json
- name: Publish to GitHub Pages
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/_site

View File

@@ -1,31 +0,0 @@
name: build-docs
on:
push:
branches:
- master
- feature/docs
jobs:
run-docfx:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: Cysharp/DocfxTemplate
path: docs/_DocfxTemplate
- uses: Kirbyrawr/docfx-action@master
name: Docfx metadata
with:
args: metadata docs/docfx.json
- uses: Kirbyrawr/docfx-action@master
name: Docfx build
with:
args: build docs/docfx.json
- name: Publish to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/_site

View File

@@ -14,6 +14,9 @@ on:
jobs:
update-packagejson:
permissions:
actions: read
contents: write
uses: Cysharp/Actions/.github/workflows/update-packagejson.yaml@main
with:
file-path: ./src/UniTask/Assets/Plugins/UniTask/package.json
@@ -22,11 +25,13 @@ jobs:
build-dotnet:
needs: [update-packagejson]
runs-on: ubuntu-latest
permissions:
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- run: echo ${{ needs.update-packagejson.outputs.sha }}
- uses: actions/checkout@v4
- uses: Cysharp/Actions/.github/actions/checkout@main
with:
ref: ${{ needs.update-packagejson.outputs.sha }}
- uses: Cysharp/Actions/.github/actions/setup-dotnet@main
@@ -46,22 +51,24 @@ jobs:
strategy:
matrix:
unity: ["2022.3.39f1"]
runs-on: ubuntu-latest
permissions:
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Load secrets
id: op-load-secret
uses: 1password/load-secrets-action@v2
uses: 1password/load-secrets-action@92467eb28f72e8255933372f1e0707c567ce2259 # v4.0.0
with:
export-env: false
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN_PUBLIC }}
UNITY_EMAIL: "op://GitHubActionsPublic/UNITY_LICENSE/username"
UNITY_PASSWORD: "op://GitHubActionsPublic/UNITY_LICENSE/credential"
UNITY_SERIAL: "op://GitHubActionsPublic/UNITY_LICENSE/serial"
UNITY_EMAIL: "op://${{ vars.OP_VAULT_ACTIONS_PUBLIC }}/UNITY_LICENSE/username"
UNITY_PASSWORD: "op://${{ vars.OP_VAULT_ACTIONS_PUBLIC }}/UNITY_LICENSE/credential"
UNITY_SERIAL: "op://${{ vars.OP_VAULT_ACTIONS_PUBLIC }}/UNITY_LICENSE/serial"
- run: echo ${{ needs.update-packagejson.outputs.sha }}
- uses: actions/checkout@v4
- uses: Cysharp/Actions/.github/actions/checkout@main
with:
ref: ${{ needs.update-packagejson.outputs.sha }}
# Execute scripts: Export Package
@@ -92,6 +99,9 @@ jobs:
# release
create-release:
needs: [update-packagejson, build-dotnet, build-unity]
permissions:
contents: write
id-token: write # required for NuGet Trusted Publish
uses: Cysharp/Actions/.github/workflows/create-release.yaml@main
with:
commit-id: ${{ needs.update-packagejson.outputs.sha }}
@@ -105,6 +115,8 @@ jobs:
cleanup:
if: ${{ needs.update-packagejson.outputs.is-branch-created == 'true' }}
needs: [update-packagejson, build-dotnet, build-unity]
permissions:
contents: write
uses: Cysharp/Actions/.github/workflows/clean-packagejson-branch.yaml@main
with:
branch: ${{ needs.update-packagejson.outputs.branch-name }}

View File

@@ -7,4 +7,6 @@ on:
jobs:
detect:
permissions:
contents: read
uses: Cysharp/Actions/.github/workflows/prevent-github-change.yaml@main

View File

@@ -7,4 +7,8 @@ on:
jobs:
stale:
permissions:
contents: read
pull-requests: write
issues: write
uses: Cysharp/Actions/.github/workflows/stale-issue.yaml@main

15
.github/workflows/toc.yaml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: TOC Generator
on:
push:
paths:
- 'README.md'
jobs:
toc:
permissions:
contents: write
uses: Cysharp/Actions/.github/workflows/toc-generator.yaml@main
with:
TOC_TITLE: "## Table of Contents"
secrets: inherit

View File

@@ -1,15 +0,0 @@
name: TOC Generator
on:
push:
paths:
- 'README.md'
jobs:
generateTOC:
name: TOC Generator
runs-on: ubuntu-latest
steps:
- uses: technote-space/toc-generator@v4.3.1
with:
TOC_TITLE: "## Table of Contents"

14
.gitignore vendored
View File

@@ -100,7 +100,19 @@ publish
*.Publish.xml
# NuGet Packages Directory
packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
# packages # upm pacakge will use Packages
# **/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
# !**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Windows Azure Build Output
csx

27
Directory.Build.props Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)opensource.snk</AssemblyOriginatorKeyFile>
<!-- NuGet Package Information -->
<IsPackable>false</IsPackable>
<PackageVersion>$(Version)</PackageVersion>
<Company>Cysharp</Company>
<Authors>Cysharp</Authors>
<Copyright>© Cysharp, Inc.</Copyright>
<PackageTags>task;async</PackageTags>
<PackageProjectUrl>https://github.com/Cysharp/UniTask</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>$(PackageProjectUrl)</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Icon.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)Icon.png" Pack="true" PackagePath="\" />
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="\" />
<EmbeddedResource Include="$(MSBuildThisFileDirectory)LICENSE" />
</ItemGroup>
</Project>

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -33,6 +33,7 @@ For advanced tips, see blog post: [Extends UnityWebRequest via async decorator p
- [AsyncEnumerable and Async LINQ](#asyncenumerable-and-async-linq)
- [Awaitable Events](#awaitable-events)
- [Channel](#channel)
- [vs Awaitable](#vs-awaitable)
- [For Unit Testing](#for-unit-testing)
- [ThreadPool limitation](#threadpool-limitation)
- [IEnumerator.ToUniTask limitation](#ienumeratortounitask-limitation)
@@ -67,6 +68,7 @@ async UniTask<string> DemoAsync()
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>
@@ -160,7 +162,7 @@ UniTask provides three pattern of extension methods.
> Note: AssetBundleRequest has `asset` and `allAssets`, default await returns `asset`. If you want to get `allAssets`, you can use `AwaitForAllAssets()` method.
The type of `UniTask` can use utilities like `UniTask.WhenAll`, `UniTask.WhenAny`. They are like `Task.WhenAll`/`Task.WhenAny` but the return type is more useful. They return value tuples so you can deconstruct each result and pass multiple types.
The type of `UniTask` can use utilities like `UniTask.WhenAll`, `UniTask.WhenAny`, `UniTask.WhenEach`. They are like `Task.WhenAll`/`Task.WhenAny` but the return type is more useful. They return value tuples so you can deconstruct each result and pass multiple types.
```csharp
public async UniTaskVoid LoadManyAsync()
@@ -293,6 +295,8 @@ public class MyBehaviour : MonoBehaviour
}
```
After Unity 2022.2, Unity adds CancellationToken in [MonoBehaviour.destroyCancellationToken](https://docs.unity3d.com/ScriptReference/MonoBehaviour-destroyCancellationToken.html) and [Application.exitCancellationToken](https://docs.unity3d.com/ScriptReference/Application-exitCancellationToken.html).
When cancellation is detected, all methods throw `OperationCanceledException` and propagate upstream. When exception(not limited to `OperationCanceledException`) is not handled in async method, it is propagated finally to `UniTaskScheduler.UnobservedTaskException`. The default behaviour of received unhandled exception is to write log as exception. Log level can be changed using `UniTaskScheduler.UnobservedExceptionWriteLogType`. If you want to use custom behaviour, set an action to `UniTaskScheduler.UnobservedTaskException.`
And also `OperationCanceledException` is a special exception, this is silently ignored at `UnobservedTaskException`.
@@ -526,6 +530,9 @@ It indicates when to run, you can check [PlayerLoopList.md](https://gist.github.
In UniTask, await directly uses native timing, while `WithCancellation` and `ToUniTask` use specified timing. This is usually not a particular problem, but with `LoadSceneAsync`, it causes a different order of Start and continuation after await. So it is recommended not to use `LoadSceneAsync.ToUniTask`.
> Note: When using Unity 2023.1 or newer, ensure you have `using UnityEngine;` in the using statements of your file when working with new `UnityEngine.Awaitable` methods like `SceneManager.LoadSceneAsync`.
> This prevents compilation errors by avoiding the use of the `UnityEngine.AsyncOperation` version.
In the stacktrace, you can check where it is running in playerloop.
![image](https://user-images.githubusercontent.com/46207/83735571-83caea80-a68b-11ea-8d22-5e22864f0d24.png)
@@ -716,6 +723,19 @@ await UniTaskAsyncEnumerable.EveryUpdate().ForEachAsync(_ =>
}, token);
```
`UniTask.WhenEach` that is similar to .NET 9's `Task.WhenEach` can consume new way for await multiple tasks.
```csharp
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());
}
```
UniTaskAsyncEnumerable implements asynchronous LINQ, similar to LINQ in `IEnumerable<T>` or Rx in `IObservable<T>`. All standard LINQ query operators can be applied to asynchronous streams. For example, the following code shows how to apply a Where filter to a button-click asynchronous stream that runs once every two clicks.
```csharp
@@ -937,6 +957,14 @@ public class AsyncMessageBroker<T> : IDisposable
}
```
vs Awaitable
---
Unity 6 introduces the awaitable type, [Awaitable](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Awaitable.html). To put it simply, Awaitable can be considered a subset of UniTask, and in fact, Awaitable's design was influenced by UniTask. It should be able to handle PlayerLoop-based awaits, pooled Tasks, and support for cancellation with `CancellationToken` in a similar way. With its inclusion in the standard library, you may wonder whether to continue using UniTask or migrate to Awaitable. Here's a brief guide.
First, the functionality provided by Awaitable is equivalent to what coroutines offer. Instead of `yield return`, you use await; `await NextFrameAsync()` replaces `yield return null`; and there are equivalents for `WaitForSeconds` and `EndOfFrame`. However, that's the extent of it. Being coroutine-based in terms of functionality, it lacks Task-based features. In practical application development using async/await, operations like `WhenAll` are essential. Additionally, UniTask enables many frame-based operations (such as `DelayFrame`) and more flexible PlayerLoopTiming control, which are not available in Awaitable. Of course, there's no Tracker Window either.
Therefore, I recommend using UniTask for application development. UniTask is a superset of Awaitable and includes many essential features. For library development, where you want to avoid external dependencies, using Awaitable as a return type for methods would be appropriate. Awaitable can be converted to UniTask using `AsUniTask`, so there's no issue in handling Awaitable-based functionality within the UniTask library. Of course, if you don't need to worry about dependencies, using UniTask would be the best choice even for library development.
For Unit Testing
---
Unity's `[UnityTest]` attribute can test coroutine(IEnumerator) but can not test async. `UniTask.ToCoroutine` bridges async/await to coroutine so you can test async methods.
@@ -1026,6 +1054,7 @@ Use UniTask type.
| `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` |

View File

@@ -2,27 +2,27 @@ UniTask
===
[![GitHub Actions](https://github.com/Cysharp/UniTask/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/UniTask/actions) [![Releases](https://img.shields.io/github/release/Cysharp/UniTask.svg)](https://github.com/Cysharp/UniTask/releases)
为Unity提供一个高性能0GC的async/await异步方案。
为Unity提供一个高性能零堆内存分配的 async/await 异步方案。
- 基于值类型的`UniTask<T>`和自定义的 AsyncMethodBuilder 来实现0GC
- 基于值类型的`UniTask<T>`和自定义的 AsyncMethodBuilder 来实现零堆内存分配
- 使所有 Unity 的 AsyncOperations 和 Coroutines 可等待
- 基于 PlayerLoop 的任务( `UniTask.Yield`, `UniTask.Delay`, `UniTask.DelayFrame`, etc..) 可以替换所有协程操作
- 对MonoBehaviour 消息事件和 uGUI 事件进行 可等待/异步枚举
- 基于 PlayerLoop 的任务`UniTask.Yield``UniTask.Delay``UniTask.DelayFrame`等..可以替换所有协程操作
- MonoBehaviour 消息事件和 uGUI 事件进行可等待/异步枚举
- 完全在 Unity 的 PlayerLoop 上运行因此不使用Thread并且同样能在 WebGL、wasm 等平台上运行。
- 带有 Channel 和 AsyncReactiveProperty的异步 LINQ
- 带有 Channel 和 AsyncReactiveProperty 的异步 LINQ
- 提供一个 TaskTracker EditorWindow 以追踪所有 UniTask 分配来预防内存泄漏
- 与原生 Task/ValueTask/IValueTaskSource 高度兼容的行为
有关技术细节,请参阅博客文章:[UniTask v2 — Unity 的0GC async/await 以及 异步LINQ 的使用](https://medium.com/@neuecc/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd)
有关技术细节,请参阅博客文章:[UniTask v2 — 适用于 Unity 的零堆内存分配的async/await,支持异步 LINQ](https://medium.com/@neuecc/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd)
有关高级技巧,请参阅博客文章:[通过异步装饰器模式扩展 UnityWebRequest — UniTask 的高级技术](https://medium.com/@neuecc/extends-unitywebrequest-via-async-decorator-pattern-advanced-techniques-of-unitask-ceff9c5ee846)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of Contents
## 目录
- [入门](#%E5%85%A5%E9%97%A8)
- [UniTask 和 AsyncOperation 基础知识](#unitask-%E5%92%8C-asyncoperation-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86)
- [Cancellation and Exception handling](#cancellation-and-exception-handling)
- [UniTask 和 AsyncOperation 基础知识](#unitask-%E5%92%8C-asyncoperation-%E7%9A%84%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86)
- [取消和异常处理](#%E5%8F%96%E6%B6%88%E5%92%8C%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86)
- [超时处理](#%E8%B6%85%E6%97%B6%E5%A4%84%E7%90%86)
- [进度](#%E8%BF%9B%E5%BA%A6)
- [PlayerLoop](#playerloop)
@@ -32,41 +32,42 @@ UniTask
- [AsyncEnumerable 和 Async LINQ](#asyncenumerable-%E5%92%8C-async-linq)
- [可等待事件](#%E5%8F%AF%E7%AD%89%E5%BE%85%E4%BA%8B%E4%BB%B6)
- [Channel](#channel)
- [与 Awaitable 对比](#%E4%B8%8E-awaitable-%E5%AF%B9%E6%AF%94)
- [单元测试](#%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95)
- [线程池限制](#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E9%99%90%E5%88%B6)
- [IEnumerator.ToUniTask 限制](#ienumeratortounitask-%E9%99%90%E5%88%B6)
- [关于UnityEditor](#%E5%85%B3%E4%BA%8Eunityeditor)
- [与原生Task API对比](#%E4%B8%8E%E5%8E%9F%E7%94%9Ftask-api%E5%AF%B9%E6%AF%94)
- [线程池限制](#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E9%99%90%E5%88%B6)
- [IEnumerator.ToUniTask 限制](#ienumeratortounitask-%E7%9A%84%E9%99%90%E5%88%B6)
- [关于 UnityEditor](#%E5%85%B3%E4%BA%8E-unityeditor)
- [与原生 Task API 对比](#%E4%B8%8E%E5%8E%9F%E7%94%9F-task-api-%E5%AF%B9%E6%AF%94)
- [池化配置](#%E6%B1%A0%E5%8C%96%E9%85%8D%E7%BD%AE)
- [Profiler下的分配](#profiler%E4%B8%8B%E7%9A%84%E5%88%86%E9%85%8D)
- [Profiler 下的堆内存分配](#profiler-%E4%B8%8B%E7%9A%84%E5%A0%86%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D)
- [UniTaskSynchronizationContext](#unitasksynchronizationcontext)
- [API References](#api-references)
- [UPM Package](#upm-package)
- [API 文档](#api-%E6%96%87%E6%A1%A3)
- [UPM ](#upm-%E5%8C%85)
- [通过 git URL 安装](#%E9%80%9A%E8%BF%87-git-url-%E5%AE%89%E8%A3%85)
- [通过 OpenUPM 安装](#%E9%80%9A%E8%BF%87-openupm-%E5%AE%89%E8%A3%85)
- [.NET Core](#net-core)
- [License](#license)
- [关于 .NET Core](#%E5%85%B3%E4%BA%8E-net-core)
- [许可证](#%E8%AE%B8%E5%8F%AF%E8%AF%81)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
入门
---
通过[UniTask/releases](https://github.com/Cysharp/UniTask/releases)页面中提供的[UPM 包](https://github.com/Cysharp/UniTask#upm-package)或资产包 ( `UniTask.*.*.*.unitypackage`)安装。
通过[UniTask/releases](https://github.com/Cysharp/UniTask/releases)页面中提供的[UPM 包](https://github.com/Cysharp/UniTask#upm-package)或资产包`UniTask.*.*.*.unitypackage`安装。
```csharp
// 使用 UniTask 所需的命名空间
using Cysharp.Threading.Tasks;
// 可以返回一个形如 UniTask<T>(或 UniTask) 的类型这种类型事为Unity定制的作为替代原生Task<T>的轻量级方案
// 为Unity集成的 0GC快速调用0消耗的 async/await 方案
// 可以返回一个形如 UniTask<T>(或 UniTask) 的类型这种类型事为Unity定制的作为替代原生 Task<T> 的轻量级方案
// 为 Unity 集成的零堆内存分配快速调用0消耗的 async/await 方案
async UniTask<string> DemoAsync()
{
// 可以等待一个Unity异步对象
// 可以等待一个 Unity 异步对象
var asset = await Resources.LoadAsync<TextAsset>("foo");
var txt = (await UnityWebRequest.Get("https://...").SendWebRequest()).downloadHandler.text;
await SceneManager.LoadSceneAsync("scene2");
// .WithCancellation 会启用取消功能GetCancellationTokenOnDestroy 表示获取一个依赖对象生命周期的 Cancel 句柄,当对象被销毁时,将会调用这个 Cancel 句柄,从而实现取消的功能
// 在 Unity 2022.2之后,您可以在 MonoBehaviour 中使用`destroyCancellationToken`
var asset2 = await Resources.LoadAsync<TextAsset>("bar").WithCancellation(this.GetCancellationTokenOnDestroy());
// .ToUniTask 可接收一个 progress 回调以及一些配置参数Progress.Create 是 IProgress<T> 的轻量级替代方案
@@ -78,29 +79,34 @@ async UniTask<string> DemoAsync()
// yield return new WaitForSeconds/WaitForSecondsRealtime 的替代方案
await UniTask.Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false);
// 可以等待任何 playerloop 的生命周期(PreUpdate, Update, LateUpdate, 等...)
// 可以等待任何 playerloop 的生命周期PreUpdateUpdateLateUpdate等)
await UniTask.Yield(PlayerLoopTiming.PreLateUpdate);
// yield return null 替代方案
// yield return null 替代方案
await UniTask.Yield();
await UniTask.NextFrame();
// WaitForEndOfFrame 替代方案 (需要 MonoBehaviour(CoroutineRunner))
// WaitForEndOfFrame 替代方案
#if UNITY_2023_1_OR_NEWER
await UniTask.WaitForEndOfFrame();
#else
// 需要 MonoBehaviourCoroutineRunner
await UniTask.WaitForEndOfFrame(this); // this是一个 MonoBehaviour
#endif
// yield return new WaitForFixedUpdate 替代方案,(和 UniTask.Yield(PlayerLoopTiming.FixedUpdate) 效果一样)
// yield return new WaitForFixedUpdate 替代方案,(等同于 UniTask.Yield(PlayerLoopTiming.FixedUpdate)
await UniTask.WaitForFixedUpdate();
// yield return WaitUntil 替代方案
// yield return WaitUntil 替代方案
await UniTask.WaitUntil(() => isActive == false);
// WaitUntil展,指定某个值改变时触发
// WaitUntil展,指定某个值改变时触发
await UniTask.WaitUntilValueChanged(this, x => x.isActive);
// 可以直接 await 一个 IEnumerator 协程
// 可以直接 await 一个 IEnumerator 协程
await FooCoroutineEnumerator();
// 可以直接 await 一个原生 task
// 可以直接 await 一个原生 task
await Task.Run(() => 100);
// 多线程示例,在此行代码后的内容都运行在一个线程池上
@@ -108,7 +114,7 @@ async UniTask<string> DemoAsync()
/* 工作在线程池上的代码 */
// 转回主线程
// 转回主线程(等同于 UniRx 的`ObserveOnMainThread`
await UniTask.SwitchToMainThread();
// 获取异步的 webrequest
@@ -125,37 +131,37 @@ async UniTask<string> DemoAsync()
// 构造一个 async-wait并通过元组语义轻松获取所有结果
var (google, bing, yahoo) = await UniTask.WhenAll(task1, task2, task3);
// WhenAll简写形式
// WhenAll简写形式,元组可以直接 await
var (google2, bing2, yahoo2) = await (task1, task2, task3);
// 返回一个异步值,或者也可以使用`UniTask`(无结果), `UniTaskVoid`(协程,不可等待)
// 返回一个异步值,或者也可以使用`UniTask`无结果`UniTaskVoid`不可等待
return (asset as TextAsset)?.text ?? throw new InvalidOperationException("Asset not found");
}
```
UniTask 和 AsyncOperation 基础知识
UniTask 和 AsyncOperation 基础知识
---
UniTask 功能依赖于 C# 7.0( [task-like custom async method builder feature](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md) ) 所以需要的 Unity 最低版本是`Unity 2018.3` ,官方支持的最低版本是`Unity 2018.4.13f1`.
UniTask 功能依赖于 C# 7.0[task-like custom async method builder feature](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md)所以需要`Unity 2018.3`之后的版本,官方支持的最低版本是`Unity 2018.4.13f1`
为什么需要 UniTask自定义task对象因为原生 Task 太重,与 Unity 线程单线程相性不好。UniTask 不使用线程和 SynchronizationContext/ExecutionContext,因为 Unity 的异步对象由 Unity 的引擎层自动调度。它实现了更快和更低的分配并且与Unity完全兼容。
为什么需要 UniTask自定义task对象因为原生 Task 太重,与 Unity 线程(单线程)相性不好。因为 Unity 的异步对象由 Unity 的引擎层自动调度,所以 UniTask 不使用线程和 SynchronizationContext/ExecutionContext。它实现了更快和更低的分配并且与Unity完全兼容。
可以在使用 `using Cysharp.Threading.Tasks;`时对 `AsyncOperation` `ResourceRequest``AssetBundleRequest` `AssetBundleCreateRequest` `UnityWebRequestAsyncOperation` `AsyncGPUReadbackRequest` `IEnumerator`以及其他的异步操作进行 await
可以在使用`using Cysharp.Threading.Tasks;`时对`AsyncOperation``ResourceRequest``AssetBundleRequest``AssetBundleCreateRequest``UnityWebRequestAsyncOperation``AsyncGPUReadbackRequest``IEnumerator`以及其他的异步操作进行 await
UniTask 提供了三种模式的扩展方法。
```csharp
* await asyncOperation;
* .WithCancellation(CancellationToken);
* .ToUniTask(IProgress, PlayerLoopTiming, CancellationToken);
await asyncOperation;
.WithCancellation(CancellationToken);
.ToUniTask(IProgress, PlayerLoopTiming, CancellationToken);
```
`WithCancellation``ToUniTask`的简化版本,两者都返回`UniTask`。有关 cancellation 的详细信息,请参阅:[取消和异常处理](https://github.com/Cysharp/UniTask#cancellation-and-exception-handling)部分。
> 注意await 会在 PlayerLoop 执行await对象的相应native生命周期方法时返回如果条件满足的话而 WithCancellation 和 ToUniTask 是从指定的 PlayerLoop 生命周期执行时返回。有关 PlayLoop生命周期 的详细信息,请参阅:[PlayerLoop](https://github.com/Cysharp/UniTask#playerloop)部分。
> 注意: AssetBundleRequest 有`asset`和`allAssets`,默认 await 返回`asset`。如果想得到`allAssets`可以使用`AwaitForAllAssets()`方法。
> 注意: AssetBundleRequest 有`asset`和`allAssets`,默认 await 返回`asset`。如果想得到`allAssets`可以使用`AwaitForAllAssets()`方法。
`UniTask`可以使用`UniTask.WhenAll``UniTask.WhenAny`等实用函数。它们就像`Task.WhenAll`/`Task.WhenAny`但它们返回内容,这很有用。它们会返回值元组,因此您可以传递多种类型并解构每个结果。
`UniTask`可以使用`UniTask.WhenAll``UniTask.WhenAny``UniTask.WhenEach`等实用函数。它们就像`Task.WhenAll``Task.WhenAny`但它们返回的数据类型更好用。它们会返回值元组,因此您可以传递多种类型并解构每个结果。
```csharp
public async UniTaskVoid LoadManyAsync()
@@ -174,7 +180,7 @@ async UniTask<Sprite> LoadAsSprite(string path)
}
```
如果你想转换一个回调逻辑块,让它变成UniTask的话,可以使用 `UniTaskCompletionSource<T>` `TaskCompletionSource<T>`的轻量级魔改版)
如果您想要将一个回调转换为 UniTask可以使用`UniTaskCompletionSource<T>`,它是`TaskCompletionSource<T>`的轻量级版本。
```csharp
public UniTask<int> WrapByUniTaskCompletionSource()
@@ -182,47 +188,43 @@ public UniTask<int> WrapByUniTaskCompletionSource()
var utcs = new UniTaskCompletionSource<int>();
// 当操作完成时,调用 utcs.TrySetResult();
// 当操作失败时, 调用 utcs.TrySetException();
// 当操作取消时, 调用 utcs.TrySetCanceled();
// 当操作失败时调用 utcs.TrySetException();
// 当操作取消时调用 utcs.TrySetCanceled();
return utcs.Task; //本质上就是返回了一个 UniTask<int>
}
```
您可以进行如下转换
您可以进行如下转换<br>-`Task` -> `UniTask `:使用`AsUniTask`<br>-`UniTask` -> `UniTask<AsyncUnit>`:使用 `AsAsyncUnitUniTask`<br>-`UniTask<T>` -> `UniTask`:使用 `AsUniTask``UniTask<T>` -> `UniTask`的转换是无消耗的。
- `Task` -> `UniTask `: 使用`AsUniTask`
- `UniTask` -> `UniTask<AsyncUnit>`: 使用 `AsAsyncUnitUniTask`
- `UniTask<T>` -> `UniTask`: 使用 `AsUniTask`,这两者的转换是无消耗的
如果您想将异步转换为协程,您可以使用`.ToCoroutine()`,这对于您想只允许使用协程系统大有帮助。
如果你想将异步转换为协程,你可以使用`.ToCoroutine()`,如果你只想允许使用协程系统,这很有用
UniTask 不能 await 两次。这是与.NET Standard 2.1 中引入的[ValueTask/IValueTaskSource](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask-1?view=netcore-3.1)具有相同的约束
UniTask 不能await两次。这是与.NET Standard 2.1 中引入的[ValueTask/IValueTaskSource](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask-1?view=netcore-3.1)相同的约束。
> 永远不应在 ValueTask 实例上执行以下操作:
> 千万不要对 `ValueTask<TResult>` 实例执行以下操作:
>
> - 多次await实例。
> - 多次调用 AsTask。
> - 在操作尚未完成时调用 .Result 或 .GetAwaiter().GetResult(),多次调用也是不允许的
> - 混用上述行为更是不被允许的
> - 在操作尚未完成时调用 .Result 或 .GetAwaiter().GetResult()或对它们进行多次调用。
> - 对实例进行上述多种操作
>
> 如果您执行上述任何操作,则结果是未定义。
> 如果您执行上述任何操作,则结果是未定义
```csharp
var task = UniTask.DelayFrame(10);
await task;
await task; // 寄了, 抛出异常
await task; // 错误,抛出异常
```
如果实在需要多次await一个异步操作可以使用`UniTask.Lazy`来支持多次调用`.Preserve()`同样允许多次调用由UniTask内部缓存结果)。这种方法在函数范围内有多调用时很有用。
如果实在需要多次 await 一个异步操作,可以使用支持多次调用的`UniTask.Lazy``.Preserve()`同样允许多次调用(由 UniTask 内部缓存结果)。这种方法在函数范围内有多调用时很有用。
同样的`UniTaskCompletionSource`可以在同一个地方被await多次或者在很多不同的地方被await。
同样的`UniTaskCompletionSource`可以在同一个地方被 await 多次,或者在很多不同的地方被 await。
Cancellation and Exception handling
取消和异常处理
---
一些 UniTask 工厂方法有一个`CancellationToken cancellationToken = default`参数。Unity 的一些异步操作也有`WithCancellation(CancellationToken)``ToUniTask(..., CancellationToken cancellation = default)`展方法。
一些 UniTask 工厂方法有一个`CancellationToken cancellationToken = default`参数。Unity 的一些异步操作也有`WithCancellation(CancellationToken)``ToUniTask(..., CancellationToken cancellation = default)`展方法。
可以传递原生[`CancellationTokenSource`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource)给参数CancellationToken
可以通过原生[`CancellationTokenSource`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource)CancellationToken 传递给参数
```csharp
var cts = new CancellationTokenSource();
@@ -237,14 +239,14 @@ await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().WithCancellati
await UniTask.DelayFrame(1000, cancellationToken: cts.Token);
```
CancellationToken 可以由`CancellationTokenSource`或 MonoBehaviour 的`GetCancellationTokenOnDestroy`扩展方法创建。
CancellationToken 可通过`CancellationTokenSource`或 MonoBehaviour 的扩展方法`GetCancellationTokenOnDestroy`创建。
```csharp
// 这个CancellationTokenSource和this GameObject生命周期相同当this GameObject Destroy的时候就会执行Cancel
// 这个 CancellationToken 的生命周期与 GameObject 的相同
await UniTask.DelayFrame(1000, cancellationToken: this.GetCancellationTokenOnDestroy());
```
对于链式取消,所有异步方法都建议最后一个参数接受`CancellationToken cancellationToken`,并将`CancellationToken`从头传递到尾。
对于链式取消,建议所有异步方法最后一个参数接受`CancellationToken cancellationToken`,并将`CancellationToken`从头传递到尾。
```csharp
await FooAsync(this.GetCancellationTokenOnDestroy());
@@ -262,7 +264,7 @@ async UniTask BarAsync(CancellationToken cancellationToken)
}
```
`CancellationToken`表示异步的生命周期。您可以使用自定义的生命周期,而不是默认的 CancellationTokenOnDestroy。
`CancellationToken`代表了异步操作的生命周期。您可以使用默认的 CancellationTokenOnDestroy ,通过自定义的`CancellationToken`自行管理生命周期
```csharp
public class MyBehaviour : MonoBehaviour
@@ -292,9 +294,11 @@ public class MyBehaviour : MonoBehaviour
}
```
当检测到取消时,所有方法都会向上游抛出并传播`OperationCanceledException`。当异常(不限于`OperationCanceledException`)没有在异步方法中处理时,它将最终传播到`UniTaskScheduler.UnobservedTaskException`。接收到的未处理异常的默认行为是将日志写入异常。可以使用`UniTaskScheduler.UnobservedExceptionWriteLogType`更改日志级别。如果要使用自定义行为,请为`UniTaskScheduler.UnobservedTaskException.`设置一个委托
在Unity 2022.2之后Unity在[MonoBehaviour.destroyCancellationToken](https://docs.unity3d.com/ScriptReference/MonoBehaviour-destroyCancellationToken.html)和[Application.exitCancellationToken](https://docs.unity3d.com/ScriptReference/Application-exitCancellationToken.html)中添加了 CancellationToken。
`OperationCanceledException`是一个特殊的异常,会被`UnobservedTaskException`.无视
当检测到取消时,所有方法都会向上游抛出并传播`OperationCanceledException`。当异常(不限于`OperationCanceledException`)没有在异步方法中处理时,它将被传播到`UniTaskScheduler.UnobservedTaskException`。默认情况下,将接收到的未处理异常作为一般异常写入日志。可以使用`UniTaskScheduler.UnobservedExceptionWriteLogType`更改日志级别。若想对接收到未处理异常时的处理进行自定义,请为`UniTaskScheduler.UnobservedTaskException`设置一个委托
`OperationCanceledException`是一种特殊的异常,会被`UnobservedTaskException`无视
如果要取消异步 UniTask 方法中的行为,请手动抛出`OperationCanceledException`
@@ -306,7 +310,7 @@ public async UniTask<int> FooAsync()
}
```
如果您处理异常但想忽略(传播到全局cancellation处理的地方),请使用异常过滤器。
如果您只想处理异常,忽略取消操作(让其传播到全局处理 cancellation 的地方),请使用异常过滤器。
```csharp
public async UniTask<int> BarAsync()
@@ -316,14 +320,14 @@ public async UniTask<int> BarAsync()
var x = await FooAsync();
return x * 2;
}
catch (Exception ex) when (!(ex is OperationCanceledException)) // when (ex is not OperationCanceledException) at C# 9.0
catch (Exception ex) when (!(ex is OperationCanceledException)) // 在 C# 9.0 下改成 when (ex is not OperationCanceledException)
{
return -1;
}
}
```
throws/catch`OperationCanceledException`有点重,所以如果性能是一个问题,请使用`UniTask.SuppressCancellationThrow`以避免 OperationCanceledException 抛出。它将返回`(bool IsCanceled, T Result)`而不是抛出。
抛出和捕获`OperationCanceledException`有点重度,如果比较在意性能开销,请使用`UniTask.SuppressCancellationThrow`以避免抛出 OperationCanceledException 。它将返回`(bool IsCanceled, T Result)`而不是抛出异常
```csharp
var (isCanceled, _) = await UniTask.DelayFrame(10, cancellationToken: cts.Token).SuppressCancellationThrow();
@@ -333,7 +337,19 @@ if (isCanceled)
}
```
注意:仅当您在原方法直接调用SuppressCancellationThrow时才会抑制异常抛出。否则返回值将被转换且整个管道不会抑制 throws
注意:仅当您在源头处直接调用`UniTask.SuppressCancellationThrow`时才会抑制异常抛出。否则,返回值将被转换,且整个管道不会抑制异常抛出
`UniTask.Yield``UniTask.Delay`等功能依赖于 Unity 的 PlayerLoop它们在 PlayerLoop 中确定`CancellationToken`状态。
这意味着当`CancellationToken`被触发时,它们并不会立即取消。
如果要更改此行为,实现立即取消,可将`cancelImmediately`标志设置为 true。
```csharp
await UniTask.Yield(cancellationToken, cancelImmediately: true);
```
注意:比起默认行为,设置 `cancelImmediately` 为 true 并检测立即取消会有更多的性能开销。
这是因为它使用了`CancellationToken.Register`;这比在 PlayerLoop 中检查 CancellationToken 更重度。
超时处理
---
@@ -341,7 +357,7 @@ if (isCanceled)
```csharp
var cts = new CancellationTokenSource();
cts.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 5sec timeout.
cts.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 设置5s超时。
try
{
@@ -356,19 +372,19 @@ catch (OperationCanceledException ex)
}
```
> > `CancellationTokenSouce.CancelAfter`是一个原生的api。但是在 Unity 中不应该使用它,因为它依赖于线程计时器。`CancelAfterSlim`是 UniTask 的扩展方法,它使用 PlayerLoop 代替。
>
> 如果您想将超时与其他cancellation一起使用请使用`CancellationTokenSource.CreateLinkedTokenSource`.
> `CancellationTokenSouce.CancelAfter`是一个原生的 api。但是在 Unity 中不应该使用它,因为它依赖于线程计时器。`CancelAfterSlim`是 UniTask 的扩展方法,它使用 PlayerLoop 代替了线程计时器
如果您想将超时与其他 cancellation 一起使用,请使用`CancellationTokenSource.CreateLinkedTokenSource`
```csharp
var cancelToken = new CancellationTokenSource();
cancelButton.onClick.AddListener(()=>
{
cancelToken.Cancel(); // 点击按钮后取消
cancelToken.Cancel(); // 点击按钮后取消
});
var timeoutToken = new CancellationTokenSource();
timeoutToken.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 设置5s超时
timeoutToken.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 设置5s超时
try
{
@@ -390,19 +406,19 @@ catch (OperationCanceledException ex)
}
```
优化减少每调用异步方法超时的 CancellationTokenSource 分配,您可以使用 UniTask 的`TimeoutController`.
为减少每调用异步方法时用于超时的 CancellationTokenSource 的堆内存分配,您可以使用 UniTask 的`TimeoutController`进行优化。
```csharp
TimeoutController timeoutController = new TimeoutController(); // 复用timeoutController
TimeoutController timeoutController = new TimeoutController(); // 提前创建好,以便复用。
async UniTask FooAsync()
{
try
{
// 可以通过 timeoutController.Timeout(TimeSpan) 传递到 cancellationToken.
// 可以通过 timeoutController.Timeout(TimeSpan) 把超时设置传递到 cancellationToken
await UnityWebRequest.Get("http://foo").SendWebRequest()
.WithCancellation(timeoutController.Timeout(TimeSpan.FromSeconds(5)));
timeoutController.Reset(); // 当await完成后调用Reset停止超时计时器并准备下一次复用
timeoutController.Reset(); // 当 await 完成后调用 Reset停止超时计时器并准备下一次复用
}
catch (OperationCanceledException ex)
{
@@ -414,7 +430,7 @@ async UniTask FooAsync()
}
```
如果您想将超时其他取消源一起使用,请使用`new TimeoutController(CancellationToken)`.
如果您想将超时结合其他取消源一起使用,请使用`new TimeoutController(CancellationToken)`.
```csharp
TimeoutController timeoutController;
@@ -427,11 +443,11 @@ void Start()
}
```
注意UniTask 有`.Timeout`,`.TimeoutWithoutException`方法,但是,如果可能,不要使用这些,请通过`CancellationToken`. 由于`.Timeout`作用在task外部无法停止超时任务。`.Timeout`表示超时忽略结果。如果您将一个`CancellationToken`传递给该方法,它将从任务内部执行,因此可以停止正在运行的任务。
注意UniTask 有`.Timeout``.TimeoutWithoutException`方法,但如果可以的话,尽量不要使用这些方法,请传递`CancellationToken`。因为`.Timeout`是在任务外部执行,所以无法停止超时任务。`.Timeout`意味着超时忽略结果。如果您将一个`CancellationToken`传递给该方法,它将从任务内部执行,因此可以停止正在运行的任务。
进度
---
一些Unity的异步操作具有`ToUniTask(IProgress<float> progress = null, ...)`扩展方法。
一些 Unity 的异步操作具有`ToUniTask(IProgress<float> progress = null, ...)`扩展方法。
```csharp
var progress = Progress.Create<float>(x => Debug.Log(x));
@@ -441,9 +457,9 @@ var request = await UnityWebRequest.Get("http://google.co.jp")
.ToUniTask(progress: progress);
```
您不应该使用原生的`new System.Progress<T>`,因为每次都会导致GC分配。改为使`Cysharp.Threading.Tasks.Progress`。这个 progress factory 有两个方法,`Create``CreateOnlyValueChanged`. `CreateOnlyValueChanged`仅在进度值更新时调用。
您不应该使用原生的`new System.Progress<T>`,因为每次调用它都会产生堆内存分配。请改`Cysharp.Threading.Tasks.Progress`。这个 progress 工厂类有两个方法,`Create``CreateOnlyValueChanged``CreateOnlyValueChanged`仅在进度值更新时调用。
为调用者实现 IProgress 接口会更好,因为这样可以没有 lambda 分配。
为调用者实现 IProgress 接口会更好,这样不会因使用 lambda 而产生堆内存分配。
```csharp
public class Foo : MonoBehaviour, IProgress<float>
@@ -464,7 +480,7 @@ public class Foo : MonoBehaviour, IProgress<float>
PlayerLoop
---
UniTask 在自定义[PlayerLoop](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html)上运行。UniTask 基于 playerloop 的方法(`Delay``DelayFrame``asyncOperation.ToUniTask`等)接受这个`PlayerLoopTiming`
UniTask 运行在自定义[PlayerLoop](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html)。UniTask 基于 PlayerLoop 的方法(如`Delay``DelayFrame``asyncOperation.ToUniTask`等)接受这个`PlayerLoopTiming`
```csharp
public enum PlayerLoopTiming
@@ -497,27 +513,32 @@ public enum PlayerLoopTiming
}
```
它表示何时运行,您可以查[PlayerLoopList.md](https://gist.github.com/neuecc/bc3a1cfd4d74501ad057e49efcd7bdae) Unity 的默认 playerloop 注入 UniTask 的自定义循环。
它表明了异步任务会在哪个时机运行,您可以查[PlayerLoopList.md](https://gist.github.com/neuecc/bc3a1cfd4d74501ad057e49efcd7bdae)以了解 Unity 的默认 PlayerLoop 以及注入 UniTask 的自定义循环。
`PlayerLoopTiming.Update`与协程中的`yield return null`类似,但Update(Update 和 uGUI 事件(button.onClick, etc...) 前被调用(在`ScriptRunBehaviourUpdate`时被调用),yield return null 在`ScriptRunDelayedDynamicFrameRate`时被调用。`PlayerLoopTiming.FixedUpdate`类似于`WaitForFixedUpdate`
`PlayerLoopTiming.Update`与协程中的`yield return null`类似,但它会在`ScriptRunBehaviourUpdate`时,UpdateUpdate 和 uGUI 事件(button.onClick等)之前被调用,而 yield return null `ScriptRunDelayedDynamicFrameRate`时被调用。`PlayerLoopTiming.FixedUpdate`类似于`WaitForFixedUpdate`
> `PlayerLoopTiming.LastPostLateUpdate`不等同于协程的`yield return new WaitForEndOfFrame()`. 协程的 WaitForEndOfFrame 似乎在 PlayerLoop 完成后运行。一些需要协程结束帧(`Texture2D.ReadPixels`, `ScreenCapture.CaptureScreenshotAsTexture`, `CommandBuffer`, 等) 的方法在 async/await 时无法正常工作。在这些情况下,请将 MonoBehaviour(coroutine runner) 传递给`UniTask.WaitForEndOfFrame`. 例如,`await UniTask.WaitForEndOfFrame(this);`是`yield return new WaitForEndOfFrame()`轻量级0GC的替代方案。
> `PlayerLoopTiming.LastPostLateUpdate`不等同于协程的`yield return new WaitForEndOfFrame()`协程的 WaitForEndOfFrame 似乎在 PlayerLoop 完成后运行。一些需要协程结束帧的方法(`Texture2D.ReadPixels``ScreenCapture.CaptureScreenshotAsTexture``CommandBuffer`等)在 async/await 时无法正常工作。在这些情况下,请将 MonoBehaviour(用于运行协程)传递给`UniTask.WaitForEndOfFrame`例如,`await UniTask.WaitForEndOfFrame(this);`是`yield return new WaitForEndOfFrame()`轻量级无堆内存分配的替代方案。
`yield return null``UniTask.Yield`相似但不同。`yield return null`总是返回下一帧但`UniTask.Yield`返回下一个调用。也就是说,`UniTask.Yield(PlayerLoopTiming.Update)``PreUpdate`上调用,它返回相同的帧。`UniTask.NextFrame()`保证返回下一帧,您可以认为它的行为与`yield return null`一致.
> 注意:在 Unity 2023.1或更高的版本中,`await UniTask.WaitForEndOfFrame();`不再需要 MonoBehaviour。因为它使用了`UnityEngine.Awaitable.EndOfFrameAsync`。
> UniTask.Yield(without CancellationToken) 是一种特殊类型,返回`YieldAwaitable`并在 YieldRunner 上运行。它是最轻量和最快的
`yield return null``UniTask.Yield`相似但不同。`yield return null`总是返回下一帧但`UniTask.Yield`返回下一次调用。也就是说,`UniTask.Yield(PlayerLoopTiming.Update)``PreUpdate`上调用,它返回同一帧。`UniTask.NextFrame()`保证返回下一帧,您可以认为它的行为与`yield return null`一致
`AsyncOperation`在原生生命周期返回。例如await `SceneManager.LoadSceneAsync``EarlyUpdate.UpdatePreloading`时返回,在此之后,加载的场景的`Start`方法调用自`EarlyUpdate.ScriptRunDelayedStartupFrame`。同样的,`await UnityWebRequest``EarlyUpdate.ExecuteMainThreadJobs`时返回.
> UniTask.Yield不带 CancellationToken是一种特殊类型返回`YieldAwaitable`并在 YieldRunner 上运行。它是最轻量和最快的。
在 UniTask 中await 直接使用原生生命周期,`WithCancellation``ToUniTask`可以指定使用的原生生命周期。这通常不会有问题,但是`LoadSceneAsync`在等待之后,它会导致开始和继续的不同顺序。所以建议不要使用`LoadSceneAsync.ToUniTask`
`AsyncOperation`在原生生命周期返回。例如await `SceneManager.LoadSceneAsync``EarlyUpdate.UpdatePreloading`时返回,在此之后,在`EarlyUpdate.ScriptRunDelayedStartupFrame`时调用已加载场景的`Start`方法。同样的,`await UnityWebRequest``EarlyUpdate.ExecuteMainThreadJobs`时返回
堆栈跟踪中,您可以检查它在 playerloop 中的运行位置
UniTask 中,直接 await 使用的是原生生命周期,而`WithCancellation``ToUniTask`使用的特定的生命周期。这通常不会有问题,但对于`LoadSceneAsync`,它会导致`Start`方法与 await 之后的逻辑的执行顺序错乱。所以建议不要使用`LoadSceneAsync.ToUniTask`
> 注意:在 Unity 2023.1或更高的版本中,当您使用新的`UnityEngine.Awaitable`方法(如`SceneManager.LoadSceneAsync`)时,请确保您的文件的 using 指令区域中包含`using UnityEngine;`。
> 这可以通过避免使用`UnityEngine.AsyncOperation`版本来防止编译错误。
在堆栈跟踪中,您可以检查它在 PlayerLoop 中的运行位置。
![image](https://user-images.githubusercontent.com/46207/83735571-83caea80-a68b-11ea-8d22-5e22864f0d24.png)
默认情况下UniTask 的 PlayerLoop 初始化`[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]`.
默认情况下UniTask 的 PlayerLoop 在`[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]`初始化。
在 BeforeSceneLoad 中调用方法顺序是不确定的,所以如果想在其他 BeforeSceneLoad 方法中使用 UniTask应该尝试在此之前初始化
在 BeforeSceneLoad 中调用方法,它们的执行顺序是不确定的,所以如果想在其他 BeforeSceneLoad 方法中使用 UniTask应该尝试在此之前初始化好 PlayerLoop
```csharp
// AfterAssembliesLoaded 表示将会在 BeforeSceneLoad 之前调用
@@ -529,15 +550,15 @@ public static void InitUniTaskLoop()
}
```
如果您导入 Unity 的`Entities`包,则会将自定义playerloop重置为默认值`BeforeSceneLoad`并注入 ECS 的循环。当 Unity 在 UniTask 的 initialize 方法之后调用 ECS 的 inject 方法UniTask 将不再工作
如果您导入 Unity 的`Entities`包,则会`BeforeSceneLoad`将自定义 PlayerLoop 重置为默认值,并注入 ECS 的循环。当 Unity 在 UniTask 的初始化方法执行之后调用 ECS 的注入方法UniTask 将不再起作用
为了解决这个问题,您可以在 ECS 初始化后重新初始化 UniTask PlayerLoop。
```csharp
// 获取ECS Loop.
// 获取 ECS Loop
var playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop;
// 设置UniTask PlayerLoop
// 设置 UniTask PlayerLoop
PlayerLoopHelper.Initialize(ref playerLoop);
```
@@ -551,16 +572,16 @@ void Start()
}
```
您可以通过除未使用的 PlayerLoopTiming 注入来稍微优化循环成本。您可以在初始化时调用`PlayerLoopHelper.Initialize(InjectPlayerLoopTimings)`
您可以通过除未使用的 PlayerLoopTiming 注入来稍微优化循环成本。您可以在初始化时调用`PlayerLoopHelper.Initialize(InjectPlayerLoopTimings)`
```csharp
var loop = PlayerLoop.GetCurrentPlayerLoop();
PlayerLoopHelper.Initialize(ref loop, InjectPlayerLoopTimings.Minimum); // 最小化 is Update | FixedUpdate | LastPostLateUpdate
PlayerLoopHelper.Initialize(ref loop, InjectPlayerLoopTimings.Minimum); // Minimum 就是 Update | FixedUpdate | LastPostLateUpdate
```
`InjectPlayerLoopTimings`有三个预设,`All``Standard`(除 LastPostLateUpdate 外),`Minimum``Update | FixedUpdate | LastPostLateUpdate`)。默认为全部,您可以组合自定义注入时间,例如`InjectPlayerLoopTimings.Update | InjectPlayerLoopTimings.FixedUpdate | InjectPlayerLoopTimings.PreLateUpdate`.
`InjectPlayerLoopTimings`有三个预设,`All``Standard`All 除 LastPostLateUpdate 外),`Minimum``Update | FixedUpdate | LastPostLateUpdate`)。默认为 All,您可以通过组合自定义注入的时机,例如`InjectPlayerLoopTimings.Update | InjectPlayerLoopTimings.FixedUpdate | InjectPlayerLoopTimings.PreLateUpdate`
使用未注入`PlayerLoopTiming`的[Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md)可能会出错。例如,您可以为`InjectPlayerLoopTimings.Minimum`设置`BannedSymbols.txt`
使用未注入`PlayerLoopTiming`的[Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md)可能会出错。例如,您可以像下列方式那样,`InjectPlayerLoopTimings.Minimum`设置`BannedSymbols.txt`
```txt
F:Cysharp.Threading.Tasks.PlayerLoopTiming.Initialization; Isn't injected this PlayerLoop in this project.
@@ -578,13 +599,13 @@ F:Cysharp.Threading.Tasks.PlayerLoopTiming.TimeUpdate; Isn't injected this Playe
F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastTimeUpdate; Isn't injected this PlayerLoop in this project.
```
您可以将`RS0030`严重性配置为错误。
您可以将`RS0030`严重性配置为错误。
![image](https://user-images.githubusercontent.com/46207/109150837-bb933880-77ac-11eb-85ba-4fd15819dbd0.png)
async void 与 async UniTaskVoid 对比
---
`async void`是一个原生的 C# 任务系统,因此它不在 UniTask 系统上运行。也最好不要使用它。`async UniTaskVoid``async UniTask`的轻量级版本,因为它没有等待完成并立即向`UniTaskScheduler.UnobservedTaskException`报告错误. 如果您不需要等待(即发即弃),那么使用`UniTaskVoid`会更好。不幸的是,要解除警告,您需要在尾部添加`Forget()`.
`async void`是一个原生的 C# 任务系统,因此它不在 UniTask 系统上运行。也最好不要使用它。`async UniTaskVoid``async UniTask`的轻量级版本,因为它没有等待完成并立即向`UniTaskScheduler.UnobservedTaskException`报告错误如果您不需要等待(即发即弃),那么使用`UniTaskVoid`会更好。不幸的是,要解除警告,您需要在尾部添加`Forget()`
```csharp
public async UniTaskVoid FireAndForgetMethod()
@@ -599,7 +620,7 @@ public void Caller()
}
```
UniTask 也有`Forget`方法,类似`UniTaskVoid`且效果相同。但是如果完全不需要使用`await``UniTaskVoid`会更高效。
UniTask 也有`Forget`方法,`UniTaskVoid`类似且效果相同。如果完全不需要使用`await`那么使用`UniTaskVoid`会更高效。
```csharp
public async UniTask DoAsync()
@@ -614,7 +635,7 @@ public void Caller()
}
```
要使用注册到事件的异步 lambda请不要使用`async void`. 相反,您可以使用`UniTask.Action``UniTask.UnityAction`两者都通过`async UniTaskVoid` lambda 创建委托。
要使用注册到事件的异步 lambda请不要使用`async void`您可以使用`UniTask.Action``UniTask.UnityAction`来代替,这两者都通过`async UniTaskVoid` lambda 创建委托。
```csharp
Action actEvent;
@@ -636,7 +657,7 @@ class Sample : MonoBehaviour
{
async UniTaskVoid Start()
{
// async init code.
// 异步初始化代码。
}
}
```
@@ -647,22 +668,22 @@ UniTaskTracker
![image](https://user-images.githubusercontent.com/46207/83527073-4434bf00-a522-11ea-86e9-3b3975b26266.png)
* Enable AutoReload(Toggle) - 自动重新加载。
* Reload - 重新加载视图重新扫描内存中UniTask实例并刷新界面
* GC.Collect - 调用 GC.Collect。
* Enable Tracking(Toggle) - 开始跟踪异步/等待 UniTask。性能影响低。
* Enable StackTrace(Toggle) - 在任务启动时捕获 StackTrace。性能影响高。
- Enable AutoReload(Toggle) - 自动重新加载。
- Reload - 重新加载视图重新扫描内存中UniTask实例并刷新界面
- GC.Collect - 调用 GC.Collect。
- Enable Tracking(Toggle) - 开始跟踪异步/等待 UniTask。性能影响低。
- Enable StackTrace(Toggle) - 在任务启动时捕获 StackTrace。性能影响高。
UniTaskTracker 仅用于调试用途,因为启用跟踪和捕获堆栈跟踪很有用,但会对性能产生重大影响。推荐的用法是启用跟踪和堆栈跟踪以查找任务泄漏并在完成时禁用它们。
UniTaskTracker 仅用于调试用途,因为启用跟踪和捕获堆栈跟踪很有用,但会对性能产生重大影响。推荐的用法是只在查找任务泄漏时启用跟踪和堆栈跟踪,并在使用完毕后禁用它们。
外部拓展
---
默认情况下UniTask 支持 TextMeshPro`BindTo(TMP_Text)``TMP_InputField`,并且TMP_InputField有同原生uGUI `InputField`类似的事件扩展、DOTween`Tween`作为等待和Addressables`AsyncOperationHandle``AsyncOperationHandle<T>`作为等待)。
默认情况下UniTask 支持 TextMeshPro`BindTo(TMP_Text)`像原生 uGUI `InputField` 那样的事件扩展,如`TMP_InputField`、DOTween`Tween`作为等待)和 Addressables`AsyncOperationHandle``AsyncOperationHandle<T>`作为等待)。
在单独的 asmdef 中定义,`UniTask.TextMeshPro`, `UniTask.DOTween`, `UniTask.Addressables`.
它们被定义在了`UniTask.TextMeshPro``UniTask.DOTween``UniTask.Addressables`等单独的 asmdef文件中。
Package manager 中导入软件包时,会自动启用对 TextMeshPro 和 Addressables 的支持。
但对于 DOTween 支持,则需要从 [DOTWeen assets](https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676r) 中导入并定义脚本定义符号 `UNITASK_DOTWEEN_SUPPORT` 后才能启用。
包管理器中导入软件包时,会自动启用对 TextMeshPro 和 Addressables 的支持。
但对于 DOTween 支持,则需要从[DOTWeen assets](https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676r)中导入并定义脚本定义符号`UNITASK_DOTWEEN_SUPPORT`后才能启用。
```csharp
// 动画序列
@@ -677,21 +698,21 @@ await UniTask.WhenAll(
transform.DOScale(10, 3).WithCancellation(ct));
```
DOTween 支持的默认行为( `await`, `WithCancellation`, `ToUniTask`) await tween 被终止。它适用于 Complete(true/false) 和 Kill(true/false)。但是如果你想重用tweens ( `SetAutoKill(false)`)它就不能按预期工作。如果您想等待另一个时间点Tween 中存在以下扩展方法,`AwaitForComplete`, `AwaitForPause`, `AwaitForPlay`, `AwaitForRewind`, `AwaitForStepComplete`
DOTween 支持的默认行为`await``WithCancellation``ToUniTask` 会等待到 tween 被终止。它适用于 Complete(true/false) 和 Kill(true/false)。但是如果您想复用 tweens`SetAutoKill(false)`它就不能按预期工作。如果您想等待另一个时间点Tween 中存在以下扩展方法,`AwaitForComplete``AwaitForPause``AwaitForPlay``AwaitForRewind``AwaitForStepComplete`
AsyncEnumerable 和 Async LINQ
---
Unity 2020.2 支持 C# 8.0,因此您可以使用`await foreach`. 这是异步时代的新更新符号。
Unity 2020.2 支持 C# 8.0,因此您可以使用`await foreach`这是异步时代的新更新符号。
```csharp
// Unity 2020.2, C# 8.0
// Unity 2020.2C# 8.0
await foreach (var _ in UniTaskAsyncEnumerable.EveryUpdate().WithCancellation(token))
{
Debug.Log("Update() " + Time.frameCount);
}
```
在 C# 7.3 环境中,您可以使用`ForEachAsync`方法以几乎相同的方式工作。
在 C# 7.3 环境中,您可以使用`ForEachAsync`方法以几乎相同的方式工作。
```csharp
// C# 7.3(Unity 2018.3~)
@@ -701,7 +722,20 @@ await UniTaskAsyncEnumerable.EveryUpdate().ForEachAsync(_ =>
}, token);
```
UniTaskAsyncEnumerable 实现异步 LINQ类似于 LINQ 的`IEnumerable<T>`或 Rx `IObservable<T>`。所有标准 LINQ 查询运算符都可以应用于异步流。例如,以下代码表示如何将 Where 过滤器应用于每两次单击运行一次的按钮单击异步流
`UniTask.WhenEach`类似于 .NET 9`Task.WhenEach`,它可以使用新的方式来等待多个任务
```csharp
await foreach (var result in UniTask.WhenEach(task1, task2, task3))
{
// 结果的类型为 WhenEachResult<T>。
// 它包含 `T Result` or `Exception Exception`。
// 您可以检查 `IsCompletedSuccessfully` 或 `IsFaulted` 以确定是访 `.Result` 还是 `.Exception`。
// 如果希望在 `IsFaulted` 时抛出异常并在成功时获取结果,可以使用 `GetResult()`。
Debug.Log(result.GetResult());
}
```
UniTaskAsyncEnumerable 实现了异步 LINQ类似于 LINQ 的`IEnumerable<T>`或 Rx 的 `IObservable<T>`。所有标准 LINQ 查询运算符都可以应用于异步流。例如,以下代码展示了如何将 Where 过滤器应用于每两次单击运行一次的按钮点击异步流。
```csharp
await okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).ForEachAsync(_ =>
@@ -709,7 +743,7 @@ await okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).ForEachAsy
});
```
Fire and Forget 风格(例如,事件处理),也可以使用`Subscribe`.
即发即弃(Fire and Forget风格(例如,事件处理),也可以使用`Subscribe`
```csharp
okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).Subscribe(_ =>
@@ -717,13 +751,13 @@ okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).Subscribe(_ =>
});
```
Async LINQ 在 时启用`using Cysharp.Threading.Tasks.Linq;`,并且`UniTaskAsyncEnumerable``UniTask.Linq`asmdef 中定义。
在引入`using Cysharp.Threading.Tasks.Linq;`后,异步 LINQ 将被启用,并且`UniTaskAsyncEnumerable` asmdef 文件`UniTask.Linq`中定义。
它更接近 UniRxReactive Extensions但 UniTaskAsyncEnumerable 是pull-base的异步流,而 Rx 是基于push-base异步流。请注意,尽管相似,但特不同,并且细节的行为也随之不同。
它更接近 UniRxReactive Extensions但 UniTaskAsyncEnumerable 是基于 pull 的异步流,而 Rx 是基于 push异步流。请注意,尽管它们相似,但特不同,细节也有所不同。
`UniTaskAsyncEnumerable`是类似的入口点`Enumerable`。除了标准查询运算符之外,还有其他 Unity 生成器,例如`EveryUpdate``Timer``TimerFrame``Interval``IntervalFrame``EveryValueChanged`并且还添加了额外的 UniTask 原始查询运算符,如`Append`, `Prepend`, `DistinctUntilChanged`, `ToHashSet`, `Buffer`, `CombineLatest`, `Do`, `Never`, `ForEachAsync`, `Pairwise`, `Publish`, `Queue`, `Return`, `SkipUntil`, `TakeUntil`, `SkipUntilCanceled`, `TakeUntilCanceled`, `TakeLast`, `Subscribe`
`UniTaskAsyncEnumerable`是类似`Enumerable`的入口点。除了标准查询操作符之外,还 Unity 提供了其他生成器,例如`EveryUpdate``Timer``TimerFrame``Interval``IntervalFrame``EveryValueChanged`此外,还添加了 UniTask 原生的查询操作符,如`Append``Prepend``DistinctUntilChanged``ToHashSet``Buffer``CombineLatest``Do``Never``ForEachAsync``Pairwise``Publish``Queue``Return``SkipUntil``TakeUntil``SkipUntilCanceled``TakeUntilCanceled``TakeLast``Subscribe`
以 Func 作为参数的方法具有三个额外的重载,`***Await`, `***AwaitWithCancellation`
以 Func 作为参数的方法具有三个额外的重载,另外两个是`***Await``***AwaitWithCancellation`
```csharp
Select(Func<T, TR> selector)
@@ -731,12 +765,12 @@ SelectAwait(Func<T, UniTask<TR>> selector)
SelectAwaitWithCancellation(Func<T, CancellationToken, UniTask<TR>> selector)
```
如果在 func 方法内部使用`async`,请使用***Awaitor `***AwaitWithCancellation`
如果在 func 内部使用`async`方法,请使用`***Await``***AwaitWithCancellation`
如何创建异步迭代器C# 8.0 支持异步迭代器(`async yield return`),但它只允许`IAsyncEnumerable<T>`并且当然需要 C# 8.0。UniTask 支持`UniTaskAsyncEnumerable.Create`创建自定义异步迭代器的方法
如何创建异步迭代器C# 8.0 支持异步迭代器(`async yield return`),但它只允许`IAsyncEnumerable<T>`当然需要 C# 8.0。UniTask 支持使用`UniTaskAsyncEnumerable.Create`方法来创建自定义异步迭代器。
```csharp
// IAsyncEnumerable, C# 8.0 异步迭代器. ( 不要这样用因为IAsyncEnumerable不被UniTask控制).
// IAsyncEnumerableC# 8.0 异步迭代器。(请不要这样使用,因为 IAsyncEnumerable 不被 UniTask 所控制)。
public async IAsyncEnumerable<int> MyEveryUpdate([EnumeratorCancellation]CancellationToken cancelationToken = default)
{
var frameCount = 0;
@@ -751,14 +785,14 @@ public async IAsyncEnumerable<int> MyEveryUpdate([EnumeratorCancellation]Cancell
// UniTaskAsyncEnumerable.Create 并用 `await writer.YieldAsync` 代替 `yield return`.
public IUniTaskAsyncEnumerable<int> MyEveryUpdate()
{
// writer(IAsyncWriter<T>) has `YieldAsync(value)` method.
// writer(IAsyncWriter<T>) `YieldAsync(value)` 方法。
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 writer.YieldAsync(frameCount++); // 代替 `yield return`
await UniTask.Yield();
}
});
@@ -767,7 +801,7 @@ public IUniTaskAsyncEnumerable<int> MyEveryUpdate()
可等待事件
---
所有 uGUI 组件都实现`***AsAsyncEnumerable`了异步事件流的转换。
所有 uGUI 组件都实现`***AsAsyncEnumerable`,以实现对事件的异步流的转换。
```csharp
async UniTask TripleClick()
@@ -809,7 +843,7 @@ async UniTask TripleClick(CancellationToken token)
}
```
所有 MonoBehaviour 消息事件都可以转换异步流`AsyncTriggers`,可以通过`using Cysharp.Threading.Tasks.Triggers;`进行启用,.AsyncTrigger 可以使用 UniTaskAsyncEnumerable 来创建,通过`GetAsync***Trigger`触发。
所有 MonoBehaviour 消息事件均可通过`AsyncTriggers`转换异步流`AsyncTriggers`可通过引入`using Cysharp.Threading.Tasks.Triggers;`来启用。`AsyncTriggers`可以使用`GetAsync***Trigger`来创建,并将它作为 UniTaskAsyncEnumerable 来触发。
```csharp
var trigger = this.GetOnCollisionEnterAsyncHandler();
@@ -817,18 +851,18 @@ await trigger.OnCollisionEnterAsync();
await trigger.OnCollisionEnterAsync();
await trigger.OnCollisionEnterAsync();
// every moves.
// 每次移动触发。
await this.GetAsyncMoveTrigger().ForEachAsync(axisEventData =>
{
});
```
`AsyncReactiveProperty`,`AsyncReadOnlyReactiveProperty`是 UniTask 的 ReactiveProperty 版本。将异步流值绑定到 Unity 组件Text/Selectable/TMP/Text`BindTo``IUniTaskAsyncEnumerable<T>`扩展方法。
`AsyncReactiveProperty``AsyncReadOnlyReactiveProperty`是 UniTask 的 ReactiveProperty 版本。`BindTo``IUniTaskAsyncEnumerable<T>`扩展方法,可以把异步流值绑定到 Unity 组件Text/Selectable/TMP/Text
```csharp
var rp = new AsyncReactiveProperty<int>(99);
// AsyncReactiveProperty 本身是 IUniTaskAsyncEnumerable, 可以通过LINQ进行查询
// AsyncReactiveProperty 本身是 IUniTaskAsyncEnumerable可以通过 LINQ 进行查询
rp.ForEachAsync(x =>
{
Debug.Log(x);
@@ -851,16 +885,16 @@ var rorp = rp.CombineLatest(rp2, (x, y) => (x, y)).ToReadOnlyAsyncReactiveProper
在序列中的异步处理完成之前pull-based异步流不会获取下一个值。这可能会从按钮等推送类型的事件中溢出数据。
```csharp
// 在3s完成无法获取event
// 在3s延迟结束前,无法获取 event
await button.OnClickAsAsyncEnumerable().ForEachAwaitAsync(async x =>
{
await UniTask.Delay(TimeSpan.FromSeconds(3));
});
```
很有用(防止双击),但有时没用。
(在防止双击方面)是有用的,但有时也并非都有用。
使用`Queue()`方法还将在异步处理期间对事件进行排队。
使用`Queue()`方法在异步处理期间也会对事件进行排队。
```csharp
// 异步处理中对 message 进行排队
@@ -870,7 +904,7 @@ await button.OnClickAsAsyncEnumerable().Queue().ForEachAwaitAsync(async x =>
});
```
或使用`Subscribe`, fire and forget 风格
或使用即发即弃风格的`Subscribe`
```csharp
button.OnClickAsAsyncEnumerable().Subscribe(async x =>
@@ -883,11 +917,11 @@ Channel
---
`Channel`与[System.Threading.Tasks.Channels](https://docs.microsoft.com/en-us/dotnet/api/system.threading.channels?view=netcore-3.1)相同,类似于 GoLang Channel。
目前只支持多生产者、单消费者无界渠道。它可以`Channel.CreateSingleConsumerUnbounded<T>()`.
目前只支持多生产者、单消费者无界 Channel。它可以通过`Channel.CreateSingleConsumerUnbounded<T>()`来创建。
对于 producer(`.Writer`),用`TryWrite`推送值`TryComplete`完成通道。对于 consumer(`.Reader`),使用`TryRead``WaitToReadAsync``ReadAsync``Completion``ReadAllAsync`来读取队列的消息。
对于生产者(`.Writer`)使`TryWrite`推送值,使用`TryComplete`完成 Channel。对于消费者(`.Reader`),使用`TryRead``WaitToReadAsync``ReadAsync``Completion``ReadAllAsync`来读取队列的消息。
`ReadAllAsync`返回`IUniTaskAsyncEnumerable<T>` 查询 LINQ 运算符。Reader 只允许单消费者,但使用`.Publish()`查询运算符来启用多播消息。例如,制作 pub/sub 实用程序
`ReadAllAsync`返回`IUniTaskAsyncEnumerable<T>` 因此可以使用 LINQ 操作符。Reader 只允许单消费者,但可以使用`.Publish()`查询操作符来启用多播消息。例如,可以制作发布/订阅工具
```csharp
public class AsyncMessageBroker<T> : IDisposable
@@ -922,6 +956,14 @@ public class AsyncMessageBroker<T> : IDisposable
}
```
与 Awaitable 对比
---
Unity 6 引入了可等待类型[Awaitable](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Awaitable.html)。简而言之Awaitable 可以被认为是 UniTask 的一个子集并且事实上Awaitable的设计也受 UniTask 的影响。它应该能够处理基于 PlayerLoop 的 await池化 Task以及支持以类似的方式使用`CancellationToken`进行取消。随着它被包含在标准库中,您可能想知道是继续使用 UniTask 还是迁移到 Awaitable。以下是简要指南。
首先Awaitable 提供的功能与协程提供的功能相同。使用 await 代替`yield return``await NextFrameAsync()`代替`yield return null``WaitForSeconds``EndOfFrame`等价。然而,这只是两者之间的差异。就功能而言,它是基于协程的,缺乏基于 Task 的特性。在使用 async/await 的实际应用程序开发中,像`WhenAll`这样的操作是必不可少的。此外UniTask 支持许多基于帧的操作(如`DelayFrame`)和更灵活的 PlayerLoopTiming 控制,这些在 Awaitable 中是不可用的。当然,它也没有跟踪器窗口。
因此,我推荐在应用程序开发中使用 UniTask。UniTask 是 Awaitable 的超集,并包含了许多基本特性。对于库开发,如果您希望避免外部依赖,可以使用 Awaitable 作为方法的返回类型。因为 Awaitable 可以使用`AsUniTask`转换为 UniTask所以支持在 UniTask 库中处理基于 Awaitable 的功能。即便是在库开发中,如果您不需要担心依赖关系,使用 UniTask 也会是您的最佳选择。
单元测试
---
Unity 的`[UnityTest]`属性可以测试协程IEnumerator但不能测试异步。`UniTask.ToCoroutine`将 async/await 桥接到协程,以便您可以测试异步方法。
@@ -947,46 +989,46 @@ public IEnumerator DelayIgnore() => UniTask.ToCoroutine(async () =>
});
```
UniTask 自的单元测试是使用 Unity Test Runner 和[Cysharp/RuntimeUnitTestToolkit](https://github.com/Cysharp/RuntimeUnitTestToolkit)编写的,以 CI 集成并检查 IL2CPP 是否正常工作。
UniTask 自的单元测试是使用 Unity Test Runner 和[Cysharp/RuntimeUnitTestToolkit](https://github.com/Cysharp/RuntimeUnitTestToolkit)编写的,以集成到 CI 并检查 IL2CPP 是否正常工作。
## 线程池限制
## 线程池限制
大多数 UniTask 方法在单个线程 (PlayerLoop) 上运行,只有`UniTask.Run``Task.Run`等效)和`UniTask.SwitchToThreadPool`在线程池上运行。如果您使用线程池,它将无法与 WebGL 等平台兼容。
大多数 UniTask 方法在单个线程 (PlayerLoop) 上运行,只有`UniTask.Run`等同于`Task.Run`)和`UniTask.SwitchToThreadPool`在线程池上运行。如果您使用线程池,它将无法与 WebGL 等平台兼容。
`UniTask.Run`现在已弃用。可以改用`UniTask.RunOnThreadPool`。并且还要考虑是否可以使用`UniTask.Create``UniTask.Void`
`UniTask.Run`现在已弃用。可以改用`UniTask.RunOnThreadPool`。并且还要考虑是否可以使用`UniTask.Create``UniTask.Void`
## IEnumerator.ToUniTask 限制
## IEnumerator.ToUniTask 限制
您可以将协程IEnumerator转换为 UniTask或直接等待),但它有一些限制。
您可以将协程IEnumerator转换为 UniTask或直接 await),但它有一些限制。
- 不支持`WaitForEndOfFrame``WaitForFixedUpdate``Coroutine`
- Loop生命周期与`StartCoroutine`不一样,它使用指定`PlayerLoopTiming`并且默认情况下,`PlayerLoopTiming.Update`在 MonoBehaviour`Update``StartCoroutine`的循环之前行。
- 生命周期与`StartCoroutine`不一样,它使用指定`PlayerLoopTiming`并且默认情况下,`PlayerLoopTiming.Update`在 MonoBehaviour`Update``StartCoroutine`的循环之前行。
如果您想要从协程到异步的完全兼容转换,请使用`IEnumerator.ToUniTask(MonoBehaviour coroutineRunner)`重载。它在参数 MonoBehaviour 实例执行 StartCoroutine 并等待它在 UniTask 中完成。
如果您想要实现从协程到异步的完全兼容转换,请使用`IEnumerator.ToUniTask(MonoBehaviour coroutineRunner)`重载。它会在传入的 MonoBehaviour 实例执行 StartCoroutine 并在 UniTask 中等待它完成。
## 关于 UnityEditor
UniTask 可以像编辑器协程一样在 Unity 编辑器上运行。但是,有一些限制。
UniTask 可以像编辑器协程一样在 Unity 编辑器上运行。但有一些限制。
- UniTask.Delay 的 DelayType.DeltaTime、UnscaledDeltaTime 无法正常工作,因为它们无法在编辑器中获取 deltaTime。因此在 EditMode 运行,会自动将 DelayType 更改为`DelayType.Realtime`等待正确的时间
- UniTask.Delay 的 DelayType.DeltaTime、UnscaledDeltaTime 无法正常工作,因为它们无法在编辑器中获取 deltaTime。因此在 EditMode 运行,会自动将 DelayType 更改为能等待正确的时间的`DelayType.Realtime`
- 所有 PlayerLoopTiming 都在`EditorApplication.update`生命周期上运行。
-`-quit``-batchmode`不起作用,因为 Unity`EditorApplication.update`在单帧后不会运行并退出。相反,不要使用`-quit`手动退出`EditorApplication.Exit(0)`.
-`-quit``-batchmode`不起作用,因为 Unity 不会执行 `EditorApplication.update` 并在一帧后退出。因此,不要使用`-quit`使用`EditorApplication.Exit(0)`手动退出。
与原生 Task API 对比
---
UniTask 有许多原生的 Task-like API。此表示了一一对应的 API 是什么
UniTask 有许多原生的Task API。此表示了两者相对应的 API。
使用原生类型。
| .NET Type | UniTask Type |
| --- | --- |
| .NET 类型 | UniTask 类型 |
|---------------------------| --- |
| `IProgress<T>` | --- |
| `CancellationToken` | --- |
| `CancellationTokenSource` | --- |
使用 UniTask 类型.
使用 UniTask 类型
| .NET Type | UniTask Type |
| .NET 类型 | UniTask 类型 |
| --- | --- |
| `Task`/`ValueTask` | `UniTask` |
| `Task<T>`/`ValueTask<T>` | `UniTask<T>` |
@@ -1011,6 +1053,7 @@ UniTask 有许多原生的 Task-like API。此表显示了一一对应的 API
| `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` |
@@ -1020,7 +1063,7 @@ UniTask 有许多原生的 Task-like API。此表显示了一一对应的 API
池化配置
---
UniTask 积极缓存异步promise对象实现零分配(有关技术细节,请参阅博客文章[UniTask v2 — Unity 的零分配异步/等待,使用异步 LINQ](https://medium.com/@neuecc/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd)。默认情况下它缓存所有promise ,但您可以配置`TaskPool.SetMaxPoolSize`为您的值,该值表示每种类型的缓存大小。`TaskPool.GetCacheSizeInfo`返回池中当前缓存的对象。
UniTask 通过积极缓存异步 promise 对象实现零堆内存分配(有关技术细节,请参阅博客文章[UniTask v2 — 适用于 Unity 的零堆内存分配的async/await支持异步 LINQ](https://medium.com/@neuecc/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd))。默认情况下,它缓存所有 promise但您可以通过调用`TaskPool.SetMaxPoolSize`方法来自定义每种类型的最大缓存大小。`TaskPool.GetCacheSizeInfo`返回池中当前缓存的对象。
```csharp
foreach (var (type, size) in TaskPool.GetCacheSizeInfo())
@@ -1029,19 +1072,19 @@ foreach (var (type, size) in TaskPool.GetCacheSizeInfo())
}
```
Profiler下的分配
Profiler 下的堆内存分配
---
在 UnityEditor 中,分析器显示编译器生成的 AsyncStateMachine 的分配,但它只发生在调试(开发构建中。C# 编译器将 AsyncStateMachine 生成为 Debug 构建的类和 Release 构建结构。
在 UnityEditor 中,能从 profiler 中看到编译器生成的 AsyncStateMachine 的堆内存分配,但它只出现在Debugdevelopment构建中。C# 编译器在Debug 构建时将 AsyncStateMachine 生成为类,而在Release 构建时将其生成为结构。
Unity 从 2020.1 开始支持代码优化选项(右,页脚)。
Unity 从2020.1版本开始支持代码优化选项(位于右下角)。
![](https://user-images.githubusercontent.com/46207/89967342-2f944600-dc8c-11ea-99fc-0b74527a16f6.png)
您可以将 C# 编译器优化更改为 release 以删除开发版本中的 AsyncStateMachine 分配。此优化选项也可以通过设置`Compilation.CompilationPipeline-codeOptimization``Compilation.CodeOptimization`
在开发构建中,您可以通过将 C# 编译器优化设置为 release 模式来移除 AsyncStateMachine 的堆内存分配。此优化选项也可以通过`Compilation.CompilationPipeline-codeOptimization``Compilation.CodeOptimization`来设置
UniTaskSynchronizationContext
---
Unity 的默认 SynchronizationContext( `UnitySynchronizationContext`) 在性能方面表现不佳。UniTask 绕过`SynchronizationContext`(和`ExecutionContext`) 因此不使用它,但如果存在`async Task`,则仍然使用它。`UniTaskSynchronizationContext``UnitySynchronizationContext`性能更好的替代品。
Unity 的默认 SynchronizationContext(`UnitySynchronizationContext`) 在性能方面表现不佳。UniTask 绕过`SynchronizationContext`(和`ExecutionContext`) 因此 UniTask 不使用它,但如果存在`async Task`,则仍然使用它。`UniTaskSynchronizationContext``UnitySynchronizationContext`性能更好的替代品。
```csharp
public class SyncContextInjecter
@@ -1056,45 +1099,38 @@ public class SyncContextInjecter
这是一个可选的选择,并不总是推荐;`UniTaskSynchronizationContext`性能不如`async UniTask`,并且不是完整的 UniTask 替代品。它也不保证与`UnitySynchronizationContext`完全兼容
API References
API 文档
---
UniTask 的 API 参考由[DocFX](https://dotnet.github.io/docfx/)和[Cysharp/DocfXTemplate托管在](https://github.com/Cysharp/DocfxTemplate)[cysharp.github.io/UniTask](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.html)上
UniTask 的 API 文档托管在[cysharp.github.io/UniTask](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.html),使用[DocFX](https://dotnet.github.io/docfx/)和[Cysharp/DocfXTemplate](https://github.com/Cysharp/DocfxTemplate)生成
例如UniTask 的工厂方法可以在[UniTask#methods](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.UniTask.html#methods-1)中看到。UniTaskAsyncEnumerable 的工厂/扩展方法可以在[UniTaskAsyncEnumerable#methods](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.Linq.UniTaskAsyncEnumerable.html#methods-1)中看到
例如UniTask 的工厂方法可以在[UniTask#methods](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.UniTask.html#methods-1)中查阅。UniTaskAsyncEnumerable 的工厂方法和扩展方法可以在[UniTaskAsyncEnumerable#methods](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.Linq.UniTaskAsyncEnumerable.html#methods-1)中查阅
UPM Package
UPM
---
### 通过 git URL 安装
需要支持 git 包路径查询参数的 unity 版本Unity >= 2019.3.4f1Unity >= 2020.1a21)。您可以添加`https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask`到包管理器
需要支持 git 包路径查询参数的 Unity 版本Unity >= 2019.3.4f1Unity >= 2020.1a21)。您可以在包管理器中添加`https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask`
![image](https://user-images.githubusercontent.com/46207/79450714-3aadd100-8020-11ea-8aae-b8d87fc4d7be.png)
![image](https://user-images.githubusercontent.com/46207/83702872-e0f17c80-a648-11ea-8183-7469dcd4f810.png)
或添加`"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"``Packages/manifest.json`.
`Packages/manifest.json`添加`"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"`
如果要设置目标版本,UniTask 使用`*.*.*`发布标签,因此您可以指定一个版本,如`#2.1.0`. 例如`https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.1.0`.
UniTask 使用`*.*.*`发布标签来指定版本,因此如果您要设置指定版本,您可以在后面添加像`#2.1.0`这样的版本标签。例如`https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.1.0`
### 通过 OpenUPM 安装
该软件包在[openupm 注册表](https://openupm.com/)中可用。建议通过[openupm-cli](https://github.com/openupm/openupm-cli)安装。
```
openupm add com.cysharp.unitask
```
.NET Core
关于 .NET Core
---
对于 .NET Core请使用 NuGet。
> PM> Install-Package [UniTask](https://www.nuget.org/packages/UniTask)
.NET Core 版本的 UniTask 是 Unity UniTask 的子集,移除了 PlayerLoop 依赖的方法。
.NET Core 版本的 UniTask 是 Unity 版本的 UniTask 的子集,移除了依赖 PlayerLoop 的方法。
它以比标准 Task/ValueTask 更高的性能运行,但在使用时应注意忽略 ExecutionContext/SynchronizationContext。`AsyncLocal`也不起作用,因为它忽略了 ExecutionContext。
相比于原生 TaskValueTask,它能以更高的性能运行,但在使用时应注意忽略 ExecutionContextSynchronizationContext。因为它忽略了 ExecutionContext`AsyncLocal`也不起作用
如果您在内部使用 UniTask但将 ValueTask 作为外部 API 提供,您可以编写如下(受[PooledAwait](https://github.com/mgravell/PooledAwait)启发)代码
如果您在内部使用 UniTask但将 ValueTask 作为外部 API 提供,您可以编写如下代码(受[PooledAwait](https://github.com/mgravell/PooledAwait)启发)。
```csharp
public class ZeroAllocAsyncAwaitInDotNetCore
@@ -1125,10 +1161,10 @@ public ValueTask TestAsync()
}
```
.NET Core 版本允许用户在与Unity共享代码时例如[CysharpOnion](https://github.com/Cysharp/MagicOnion/)像使用接口一样使用UniTask。.NET Core 版本的 UniTask 可以提供丝滑的代码共享体验
.NET Core 版本的 UniTask 是为了让用户在与 Unity 共享代码时(例如使用[CysharpOnion](https://github.com/Cysharp/MagicOnion/)能够将 UniTask 用作接口。.NET Core 版本的 UniTask 使得代码共享更加顺畅
WhenAll 等实用方法作为 UniTask 的补充,由[Cysharp/ValueTaskSupplement](https://github.com/Cysharp/ValueTaskSupplement)提供。
[Cysharp/ValueTaskSupplement](https://github.com/Cysharp/ValueTaskSupplement)提供了一些实用方法,如 WhenAll这些方法等效于 UniTask
License
许可证
---
仓库基于MIT协议
库采用MIT许可证

View File

@@ -4,7 +4,11 @@
"src": [
{
"files": [
"UniTask/Assets/Plugins/UniTask/Runtime/**/*.cs"
"UniTask/Library/ScriptAssemblies/UniTask*.dll"
],
"exclude": [
"UniTask/Library/ScriptAssemblies/UniTask.Tests.dll",
"UniTask/Library/ScriptAssemblies/UniTask.Tests.Editor.dll"
],
"src": "../src"
}
@@ -54,7 +58,6 @@
}
],
"dest": "_site",
"globalMetadataFiles": [],
"fileMetadataFiles": [],
"template": [

View File

@@ -10,27 +10,11 @@
<NoWarn>$(NoWarn);CS1591</NoWarn>
<!-- NuGet Packaging -->
<Id>UniTask</Id>
<PackageVersion>$(Version)</PackageVersion>
<Company>Cysharp</Company>
<Authors>Cysharp</Authors>
<Copyright>© Cysharp, Inc.</Copyright>
<PackageTags>task;async</PackageTags>
<Description>Provides an efficient async/await integration to Unity and .NET Core.</Description>
<PackageProjectUrl>https://github.com/Cysharp/UniTask</PackageProjectUrl>
<RepositoryUrl>$(PackageProjectUrl)</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Icon.png</PackageIcon>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>opensource.snk</AssemblyOriginatorKeyFile>
<IsPackable>true</IsPackable>
<Id>UniTask</Id>
<Description>Provides an efficient async/await integration to Unity and .NET Core.</Description>
</PropertyGroup>
<ItemGroup>
<None Include="Icon.png" Pack="true" PackagePath="/" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\UniTask\Assets\Plugins\UniTask\Runtime\**\*.cs" Exclude="&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Editor\*.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\Triggers\*.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\Linq\UnityExtensions\*.cs;&#xD;&#xA; &#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\Internal\UnityEqualityComparer.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\Internal\DiagnosticsExtensions.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\Internal\PlayerLoopRunner.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\Internal\ContinuationQueue.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\Internal\UnityWebRequestExtensions.cs;&#xD;&#xA; &#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\UniTaskSynchronizationContext.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\CancellationTokenSourceExtensions.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\EnumeratorAsyncExtensions.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\TimeoutController.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\PlayerLoopHelper.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\PlayerLoopTimer.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\UniTask.Delay.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\UniTask.Run.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\UniTask.Bridge.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\UniTask.WaitUntil.cs;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\UnityAsyncExtensions.*;&#xD;&#xA;..\UniTask\Assets\Plugins\UniTask\Runtime\UnityBindingExtensions.cs;&#xD;&#xA;" />
<Compile Remove="..\UniTask\Assets\Plugins\UniTask\Runtime\_InternalVisibleTo.cs" />

View File

@@ -4,7 +4,6 @@
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>NetCoreSandbox</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
@@ -16,8 +15,6 @@
<ItemGroup>
<ProjectReference Include="..\UniTask.NetCore\UniTask.NetCore.csproj" />
<ProjectReference Include="..\UniTask.Analyzer\UniTask.Analyzer.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>Analyzer</OutputItemType>

View File

@@ -2,9 +2,6 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
<RootNamespace>NetCoreTests</RootNamespace>
</PropertyGroup>

View File

@@ -0,0 +1,69 @@
using Cysharp.Threading.Tasks;
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace NetCoreTests
{
public class WhenEachTest
{
[Fact]
public async Task Each()
{
var a = Delay(1, 3000);
var b = Delay(2, 1000);
var c = Delay(3, 2000);
var l = new List<int>();
await foreach (var item in UniTask.WhenEach(a, b, c))
{
l.Add(item.Result);
}
l.Should().Equal(2, 3, 1);
}
[Fact]
public async Task Error()
{
var a = Delay2(1, 3000);
var b = Delay2(2, 1000);
var c = Delay2(3, 2000);
var l = new List<WhenEachResult<int>>();
await foreach (var item in UniTask.WhenEach(a, b, c))
{
l.Add(item);
}
l[0].IsCompletedSuccessfully.Should().BeTrue();
l[0].IsFaulted.Should().BeFalse();
l[0].Result.Should().Be(2);
l[1].IsCompletedSuccessfully.Should().BeFalse();
l[1].IsFaulted.Should().BeTrue();
l[1].Exception.Message.Should().Be("ERROR");
l[2].IsCompletedSuccessfully.Should().BeTrue();
l[2].IsFaulted.Should().BeFalse();
l[2].Result.Should().Be(1);
}
async UniTask<int> Delay(int id, int sleep)
{
await Task.Delay(sleep);
return id;
}
async UniTask<int> Delay2(int id, int sleep)
{
await Task.Delay(sleep);
if (id == 3) throw new Exception("ERROR");
return id;
}
}
}

View File

@@ -184,6 +184,78 @@ namespace Cysharp.Threading.Tasks
return () => asyncAction(state).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T arg) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T> UnityAction<T>(Func<T, UniTaskVoid> asyncAction)
{
return (arg) => asyncAction(arg).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1> UnityAction<T0, T1>(Func<T0, T1, UniTaskVoid> asyncAction)
{
return (arg0, arg1) => asyncAction(arg0, arg1).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1, T2> UnityAction<T0, T1, T2>(Func<T0, T1, T2, UniTaskVoid> asyncAction)
{
return (arg0, arg1, arg2) => asyncAction(arg0, arg1, arg2).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2, T3 arg3) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1, T2, T3> UnityAction<T0, T1, T2, T3>(Func<T0, T1, T2, T3, UniTaskVoid> asyncAction)
{
return (arg0, arg1, arg2, arg3) => asyncAction(arg0, arg1, arg2, arg3).Forget();
}
// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T arg, CancellationToken cancellationToken) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T> UnityAction<T>(Func<T, CancellationToken, UniTaskVoid> asyncAction, CancellationToken cancellationToken)
{
return (arg) => asyncAction(arg, cancellationToken).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, CancellationToken cancellationToken) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1> UnityAction<T0, T1>(Func<T0, T1, CancellationToken, UniTaskVoid> asyncAction, CancellationToken cancellationToken)
{
return (arg0, arg1) => asyncAction(arg0, arg1, cancellationToken).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2, CancellationToken cancellationToken) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1, T2> UnityAction<T0, T1, T2>(Func<T0, T1, T2, CancellationToken, UniTaskVoid> asyncAction, CancellationToken cancellationToken)
{
return (arg0, arg1, arg2) => asyncAction(arg0, arg1, arg2, cancellationToken).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2, T3 arg3, CancellationToken cancellationToken) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1, T2, T3> UnityAction<T0, T1, T2, T3>(Func<T0, T1, T2, T3, CancellationToken, UniTaskVoid> asyncAction, CancellationToken cancellationToken)
{
return (arg0, arg1, arg2, arg3) => asyncAction(arg0, arg1, arg2, arg3, cancellationToken).Forget();
}
#endif
/// <summary>
@@ -202,6 +274,22 @@ namespace Cysharp.Threading.Tasks
return new UniTask<T>(new DeferPromise<T>(factory), 0);
}
/// <summary>
/// Defer the task creation just before call await.
/// </summary>
public static UniTask Defer<TState>(TState state, Func<TState, UniTask> factory)
{
return new UniTask(new DeferPromiseWithState<TState>(state, factory), 0);
}
/// <summary>
/// Defer the task creation just before call await.
/// </summary>
public static UniTask<TResult> Defer<TState, TResult>(TState state, Func<TState, UniTask<TResult>> factory)
{
return new UniTask<TResult>(new DeferPromiseWithState<TState, TResult>(state, factory), 0);
}
/// <summary>
/// Never complete.
/// </summary>
@@ -465,6 +553,93 @@ namespace Cysharp.Threading.Tasks
}
}
sealed class DeferPromiseWithState<TState> : IUniTaskSource
{
Func<TState, UniTask> factory;
TState argument;
UniTask task;
UniTask.Awaiter awaiter;
public DeferPromiseWithState(TState argument, Func<TState, UniTask> factory)
{
this.argument = argument;
this.factory = factory;
}
public void GetResult(short token)
{
awaiter.GetResult();
}
public UniTaskStatus GetStatus(short token)
{
var f = Interlocked.Exchange(ref factory, null);
if (f != null)
{
task = f(argument);
awaiter = task.GetAwaiter();
}
return task.Status;
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
awaiter.SourceOnCompleted(continuation, state);
}
public UniTaskStatus UnsafeGetStatus()
{
return task.Status;
}
}
sealed class DeferPromiseWithState<TState, TResult> : IUniTaskSource<TResult>
{
Func<TState, UniTask<TResult>> factory;
TState argument;
UniTask<TResult> task;
UniTask<TResult>.Awaiter awaiter;
public DeferPromiseWithState(TState argument, Func<TState, UniTask<TResult>> factory)
{
this.argument = argument;
this.factory = factory;
}
public TResult GetResult(short token)
{
return awaiter.GetResult();
}
void IUniTaskSource.GetResult(short token)
{
awaiter.GetResult();
}
public UniTaskStatus GetStatus(short token)
{
var f = Interlocked.Exchange(ref factory, null);
if (f != null)
{
task = f(argument);
awaiter = task.GetAwaiter();
}
return task.Status;
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
awaiter.SourceOnCompleted(continuation, state);
}
public UniTaskStatus UnsafeGetStatus()
{
return task.Status;
}
}
sealed class NeverPromise<T> : IUniTaskSource<T>
{
static readonly Action<object> cancellationCallback = CancellationCallback;

View File

@@ -15,11 +15,21 @@ namespace Cysharp.Threading.Tasks
return new UniTask(WaitUntilPromise.Create(predicate, timing, cancellationToken, cancelImmediately, out var token), token);
}
public static UniTask WaitUntil<T>(T state, Func<T, bool> predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{
return new UniTask(WaitUntilPromise<T>.Create(state, predicate, timing, cancellationToken, cancelImmediately, out var token), token);
}
public static UniTask WaitWhile(Func<bool> predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{
return new UniTask(WaitWhilePromise.Create(predicate, timing, cancellationToken, cancelImmediately, out var token), token);
}
public static UniTask WaitWhile<T>(T state, Func<T, bool> predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{
return new UniTask(WaitWhilePromise<T>.Create(state, predicate, timing, cancellationToken, cancelImmediately, out var token), token);
}
public static UniTask WaitUntilCanceled(CancellationToken cancellationToken, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool completeImmediately = false)
{
return new UniTask(WaitUntilCanceledPromise.Create(cancellationToken, timing, completeImmediately, out var token), token);
@@ -162,6 +172,135 @@ namespace Cysharp.Threading.Tasks
}
}
sealed class WaitUntilPromise<T> : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitUntilPromise<T>>
{
static TaskPool<WaitUntilPromise<T>> pool;
WaitUntilPromise<T> nextNode;
public ref WaitUntilPromise<T> NextNode => ref nextNode;
static WaitUntilPromise()
{
TaskPool.RegisterSizeGetter(typeof(WaitUntilPromise<T>), () => pool.Size);
}
Func<T, bool> predicate;
T argument;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool cancelImmediately;
UniTaskCompletionSourceCore<object> core;
WaitUntilPromise()
{
}
public static IUniTaskSource Create(T argument, Func<T, bool> predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new WaitUntilPromise<T>();
}
result.predicate = predicate;
result.argument = argument;
result.cancellationToken = cancellationToken;
result.cancelImmediately = cancelImmediately;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var promise = (WaitUntilPromise<T>)state;
promise.core.TrySetCanceled(promise.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result);
token = result.core.Version;
return result;
}
public void GetResult(short token)
{
try
{
core.GetResult(token);
}
finally
{
if (!(cancelImmediately && cancellationToken.IsCancellationRequested))
{
TryReturn();
}
else
{
TaskTracker.RemoveTracking(this);
}
}
}
public UniTaskStatus GetStatus(short token)
{
return core.GetStatus(token);
}
public UniTaskStatus UnsafeGetStatus()
{
return core.UnsafeGetStatus();
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
core.OnCompleted(continuation, state, token);
}
public bool MoveNext()
{
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
return false;
}
try
{
if (!predicate(argument))
{
return true;
}
}
catch (Exception ex)
{
core.TrySetException(ex);
return false;
}
core.TrySetResult(null);
return false;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
predicate = default;
argument = default;
cancellationToken = default;
cancellationTokenRegistration.Dispose();
cancelImmediately = default;
return pool.TryPush(this);
}
}
sealed class WaitWhilePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitWhilePromise>
{
static TaskPool<WaitWhilePromise> pool;
@@ -288,6 +427,135 @@ namespace Cysharp.Threading.Tasks
}
}
sealed class WaitWhilePromise<T> : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitWhilePromise<T>>
{
static TaskPool<WaitWhilePromise<T>> pool;
WaitWhilePromise<T> nextNode;
public ref WaitWhilePromise<T> NextNode => ref nextNode;
static WaitWhilePromise()
{
TaskPool.RegisterSizeGetter(typeof(WaitWhilePromise<T>), () => pool.Size);
}
Func<T, bool> predicate;
T argument;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool cancelImmediately;
UniTaskCompletionSourceCore<object> core;
WaitWhilePromise()
{
}
public static IUniTaskSource Create(T argument, Func<T, bool> predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new WaitWhilePromise<T>();
}
result.predicate = predicate;
result.argument = argument;
result.cancellationToken = cancellationToken;
result.cancelImmediately = cancelImmediately;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var promise = (WaitWhilePromise<T>)state;
promise.core.TrySetCanceled(promise.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result);
token = result.core.Version;
return result;
}
public void GetResult(short token)
{
try
{
core.GetResult(token);
}
finally
{
if (!(cancelImmediately && cancellationToken.IsCancellationRequested))
{
TryReturn();
}
else
{
TaskTracker.RemoveTracking(this);
}
}
}
public UniTaskStatus GetStatus(short token)
{
return core.GetStatus(token);
}
public UniTaskStatus UnsafeGetStatus()
{
return core.UnsafeGetStatus();
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
core.OnCompleted(continuation, state, token);
}
public bool MoveNext()
{
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
return false;
}
try
{
if (predicate(argument))
{
return true;
}
}
catch (Exception ex)
{
core.TrySetException(ex);
return false;
}
core.TrySetResult(null);
return false;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
predicate = default;
argument = default;
cancellationToken = default;
cancellationTokenRegistration.Dispose();
cancelImmediately = default;
return pool.TryPush(this);
}
}
sealed class WaitUntilCanceledPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitUntilCanceledPromise>
{
static TaskPool<WaitUntilCanceledPromise> pool;

View File

@@ -0,0 +1,183 @@
using Cysharp.Threading.Tasks.Internal;
using System;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
using System.Threading;
namespace Cysharp.Threading.Tasks
{
public partial struct UniTask
{
public static IUniTaskAsyncEnumerable<WhenEachResult<T>> WhenEach<T>(IEnumerable<UniTask<T>> tasks)
{
return new WhenEachEnumerable<T>(tasks);
}
public static IUniTaskAsyncEnumerable<WhenEachResult<T>> WhenEach<T>(params UniTask<T>[] tasks)
{
return new WhenEachEnumerable<T>(tasks);
}
}
public readonly struct WhenEachResult<T>
{
public T Result { get; }
public Exception Exception { get; }
//[MemberNotNullWhen(false, nameof(Exception))]
public bool IsCompletedSuccessfully => Exception == null;
//[MemberNotNullWhen(true, nameof(Exception))]
public bool IsFaulted => Exception != null;
public WhenEachResult(T result)
{
this.Result = result;
this.Exception = null;
}
public WhenEachResult(Exception exception)
{
if (exception == null) throw new ArgumentNullException(nameof(exception));
this.Result = default;
this.Exception = exception;
}
public void TryThrow()
{
if (IsFaulted)
{
ExceptionDispatchInfo.Capture(Exception).Throw();
}
}
public T GetResult()
{
if (IsFaulted)
{
ExceptionDispatchInfo.Capture(Exception).Throw();
}
return Result;
}
public override string ToString()
{
if (IsCompletedSuccessfully)
{
return Result?.ToString() ?? "";
}
else
{
return $"Exception{{{Exception.Message}}}";
}
}
}
internal enum WhenEachState : byte
{
NotRunning,
Running,
Completed
}
internal sealed class WhenEachEnumerable<T> : IUniTaskAsyncEnumerable<WhenEachResult<T>>
{
IEnumerable<UniTask<T>> source;
public WhenEachEnumerable(IEnumerable<UniTask<T>> source)
{
this.source = source;
}
public IUniTaskAsyncEnumerator<WhenEachResult<T>> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new Enumerator(source, cancellationToken);
}
sealed class Enumerator : IUniTaskAsyncEnumerator<WhenEachResult<T>>
{
readonly IEnumerable<UniTask<T>> source;
CancellationToken cancellationToken;
Channel<WhenEachResult<T>> channel;
IUniTaskAsyncEnumerator<WhenEachResult<T>> channelEnumerator;
int completeCount;
WhenEachState state;
public Enumerator(IEnumerable<UniTask<T>> source, CancellationToken cancellationToken)
{
this.source = source;
this.cancellationToken = cancellationToken;
}
public WhenEachResult<T> Current => channelEnumerator.Current;
public UniTask<bool> MoveNextAsync()
{
cancellationToken.ThrowIfCancellationRequested();
if (state == WhenEachState.NotRunning)
{
state = WhenEachState.Running;
channel = Channel.CreateSingleConsumerUnbounded<WhenEachResult<T>>();
channelEnumerator = channel.Reader.ReadAllAsync().GetAsyncEnumerator(cancellationToken);
if (source is UniTask<T>[] array)
{
ConsumeAll(this, array, array.Length);
}
else
{
using (var rentArray = ArrayPoolUtil.Materialize(source))
{
ConsumeAll(this, rentArray.Array, rentArray.Length);
}
}
}
return channelEnumerator.MoveNextAsync();
}
static void ConsumeAll(Enumerator self, UniTask<T>[] array, int length)
{
for (int i = 0; i < length; i++)
{
RunWhenEachTask(self, array[i], length).Forget();
}
}
static async UniTaskVoid RunWhenEachTask(Enumerator self, UniTask<T> task, int length)
{
try
{
var result = await task;
self.channel.Writer.TryWrite(new WhenEachResult<T>(result));
}
catch (Exception ex)
{
self.channel.Writer.TryWrite(new WhenEachResult<T>(ex));
}
if (Interlocked.Increment(ref self.completeCount) == length)
{
self.state = WhenEachState.Completed;
self.channel.Writer.TryComplete();
}
}
public async UniTask DisposeAsync()
{
if (channelEnumerator != null)
{
await channelEnumerator.DisposeAsync();
}
if (state != WhenEachState.Completed)
{
state = WhenEachState.Completed;
channel.Writer.TryComplete(new OperationCanceledException());
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7cac24fdda5112047a1cd3dd66b542c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,386 @@
// AsyncInstantiateOperation was added since Unity 2022.3.20 / 2023.3.0b7
#if UNITY_2022_3 && !(UNITY_2022_3_0 || UNITY_2022_3_1 || UNITY_2022_3_2 || UNITY_2022_3_3 || UNITY_2022_3_4 || UNITY_2022_3_5 || UNITY_2022_3_6 || UNITY_2022_3_7 || UNITY_2022_3_8 || UNITY_2022_3_9 || UNITY_2022_3_10 || UNITY_2022_3_11 || UNITY_2022_3_12 || UNITY_2022_3_13 || UNITY_2022_3_14 || UNITY_2022_3_15 || UNITY_2022_3_16 || UNITY_2022_3_17 || UNITY_2022_3_18 || UNITY_2022_3_19)
#define UNITY_2022_SUPPORT
#endif
#if UNITY_2022_SUPPORT || UNITY_2023_3_OR_NEWER
using Cysharp.Threading.Tasks.Internal;
using System;
using System.Threading;
using UnityEngine;
namespace Cysharp.Threading.Tasks
{
public static class AsyncInstantiateOperationExtensions
{
// AsyncInstantiateOperation<T> has GetAwaiter so no need to impl
// public static UniTask<T[]>.Awaiter GetAwaiter<T>(this AsyncInstantiateOperation<T> operation) where T : Object
public static UniTask<UnityEngine.Object[]> WithCancellation<T>(this AsyncInstantiateOperation asyncOperation, CancellationToken cancellationToken)
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
}
public static UniTask<UnityEngine.Object[]> WithCancellation<T>(this AsyncInstantiateOperation asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<UnityEngine.Object[]> ToUniTask(this AsyncInstantiateOperation asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityEngine.Object[]>(cancellationToken);
if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.Result);
return new UniTask<UnityEngine.Object[]>(AsyncInstantiateOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token);
}
public static UniTask<T[]> WithCancellation<T>(this AsyncInstantiateOperation<T> asyncOperation, CancellationToken cancellationToken)
where T : UnityEngine.Object
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
}
public static UniTask<T[]> WithCancellation<T>(this AsyncInstantiateOperation<T> asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
where T : UnityEngine.Object
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<T[]> ToUniTask<T>(this AsyncInstantiateOperation<T> asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
where T : UnityEngine.Object
{
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<T[]>(cancellationToken);
if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.Result);
return new UniTask<T[]>(AsyncInstantiateOperationConfiguredSource<T>.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token);
}
sealed class AsyncInstantiateOperationConfiguredSource : IUniTaskSource<UnityEngine.Object[]>, IPlayerLoopItem, ITaskPoolNode<AsyncInstantiateOperationConfiguredSource>
{
static TaskPool<AsyncInstantiateOperationConfiguredSource> pool;
AsyncInstantiateOperationConfiguredSource nextNode;
public ref AsyncInstantiateOperationConfiguredSource NextNode => ref nextNode;
static AsyncInstantiateOperationConfiguredSource()
{
TaskPool.RegisterSizeGetter(typeof(AsyncInstantiateOperationConfiguredSource), () => pool.Size);
}
AsyncInstantiateOperation asyncOperation;
IProgress<float> progress;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool cancelImmediately;
bool completed;
UniTaskCompletionSourceCore<UnityEngine.Object[]> core;
Action<AsyncOperation> continuationAction;
AsyncInstantiateOperationConfiguredSource()
{
continuationAction = Continuation;
}
public static IUniTaskSource<UnityEngine.Object[]> Create(AsyncInstantiateOperation asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource<UnityEngine.Object[]>.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new AsyncInstantiateOperationConfiguredSource();
}
result.asyncOperation = asyncOperation;
result.progress = progress;
result.cancellationToken = cancellationToken;
result.cancelImmediately = cancelImmediately;
result.completed = false;
asyncOperation.completed += result.continuationAction;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var source = (AsyncInstantiateOperationConfiguredSource)state;
source.core.TrySetCanceled(source.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result);
token = result.core.Version;
return result;
}
public UnityEngine.Object[] GetResult(short token)
{
try
{
return core.GetResult(token);
}
finally
{
if (!(cancelImmediately && cancellationToken.IsCancellationRequested))
{
TryReturn();
}
else
{
TaskTracker.RemoveTracking(this);
}
}
}
void IUniTaskSource.GetResult(short token)
{
GetResult(token);
}
public UniTaskStatus GetStatus(short token)
{
return core.GetStatus(token);
}
public UniTaskStatus UnsafeGetStatus()
{
return core.UnsafeGetStatus();
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
core.OnCompleted(continuation, state, token);
}
public bool MoveNext()
{
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
return false;
}
if (progress != null)
{
progress.Report(asyncOperation.progress);
}
if (asyncOperation.isDone)
{
core.TrySetResult(asyncOperation.Result);
return false;
}
return true;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
asyncOperation.completed -= continuationAction;
asyncOperation = default;
progress = default;
cancellationToken = default;
cancellationTokenRegistration.Dispose();
cancelImmediately = default;
return pool.TryPush(this);
}
void Continuation(AsyncOperation _)
{
if (completed)
{
return;
}
completed = true;
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
}
else
{
core.TrySetResult(asyncOperation.Result);
}
}
}
sealed class AsyncInstantiateOperationConfiguredSource<T> : IUniTaskSource<T[]>, IPlayerLoopItem, ITaskPoolNode<AsyncInstantiateOperationConfiguredSource<T>>
where T : UnityEngine.Object
{
static TaskPool<AsyncInstantiateOperationConfiguredSource<T>> pool;
AsyncInstantiateOperationConfiguredSource<T> nextNode;
public ref AsyncInstantiateOperationConfiguredSource<T> NextNode => ref nextNode;
static AsyncInstantiateOperationConfiguredSource()
{
TaskPool.RegisterSizeGetter(typeof(AsyncInstantiateOperationConfiguredSource<T>), () => pool.Size);
}
AsyncInstantiateOperation<T> asyncOperation;
IProgress<float> progress;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool cancelImmediately;
bool completed;
UniTaskCompletionSourceCore<T[]> core;
Action<AsyncOperation> continuationAction;
AsyncInstantiateOperationConfiguredSource()
{
continuationAction = Continuation;
}
public static IUniTaskSource<T[]> Create(AsyncInstantiateOperation<T> asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource<T[]>.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new AsyncInstantiateOperationConfiguredSource<T>();
}
result.asyncOperation = asyncOperation;
result.progress = progress;
result.cancellationToken = cancellationToken;
result.cancelImmediately = cancelImmediately;
result.completed = false;
asyncOperation.completed += result.continuationAction;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var source = (AsyncInstantiateOperationConfiguredSource<T>)state;
source.core.TrySetCanceled(source.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result);
token = result.core.Version;
return result;
}
public T[] GetResult(short token)
{
try
{
return core.GetResult(token);
}
finally
{
if (!(cancelImmediately && cancellationToken.IsCancellationRequested))
{
TryReturn();
}
else
{
TaskTracker.RemoveTracking(this);
}
}
}
void IUniTaskSource.GetResult(short token)
{
GetResult(token);
}
public UniTaskStatus GetStatus(short token)
{
return core.GetStatus(token);
}
public UniTaskStatus UnsafeGetStatus()
{
return core.UnsafeGetStatus();
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
core.OnCompleted(continuation, state, token);
}
public bool MoveNext()
{
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
return false;
}
if (progress != null)
{
progress.Report(asyncOperation.progress);
}
if (asyncOperation.isDone)
{
core.TrySetResult(asyncOperation.Result);
return false;
}
return true;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
asyncOperation.completed -= continuationAction;
asyncOperation = default;
progress = default;
cancellationToken = default;
cancellationTokenRegistration.Dispose();
cancelImmediately = default;
return pool.TryPush(this);
}
void Continuation(AsyncOperation _)
{
if (completed)
{
return;
}
completed = true;
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
}
else
{
core.TrySetResult(asyncOperation.Result);
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8321f4244edfdcd4798b4fcc92a736c9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -2,7 +2,7 @@
"name": "com.cysharp.unitask",
"displayName": "UniTask",
"author": { "name": "Cysharp, Inc.", "url": "https://cysharp.co.jp/en/" },
"version": "2.5.6",
"version": "2.5.10",
"unity": "2018.4",
"description": "Provides an efficient async/await integration to Unity.",
"keywords": [ "async/await", "async", "Task", "UniTask" ],

View File

@@ -18,6 +18,7 @@ using UnityEngine.SceneManagement;
using UnityEngine.Rendering;
using System.IO;
using System.Linq.Expressions;
using UnityEngine.Events;
@@ -119,7 +120,28 @@ public class AsyncMessageBroker<T> : IDisposable
connection.Dispose();
}
}
public class WhenEachTest
{
public async UniTask Each()
{
var a = Delay(1, 3000);
var b = Delay(2, 1000);
var c = Delay(3, 2000);
var l = new List<int>();
await foreach (var item in UniTask.WhenEach(a, b, c))
{
Debug.Log(item.Result);
}
}
async UniTask<int> Delay(int id, int sleep)
{
await UniTask.Delay(sleep);
return id;
}
}
public class SandboxMain : MonoBehaviour
{
@@ -147,6 +169,19 @@ public class SandboxMain : MonoBehaviour
Debug.Log("Again");
// var foo = InstantiateAsync<SandboxMain>(this).ToUniTask();
// var tako = await foo;
//UnityAction action;
return 10;
}
@@ -557,6 +592,7 @@ public class SandboxMain : MonoBehaviour
async UniTaskVoid Start()
{
await new WhenEachTest().Each();
// UniTask.Delay(TimeSpan.FromSeconds(1)).TimeoutWithoutException

View File

@@ -13,7 +13,7 @@ OcclusionCullingSettings:
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 9
serializedVersion: 10
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
@@ -43,7 +43,6 @@ RenderSettings:
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 12
m_GIWorkflowMode: 1
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
@@ -66,9 +65,6 @@ LightmapSettings:
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_FinalGather: 0
m_FinalGatherFiltering: 1
m_FinalGatherRayCount: 256
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 0
@@ -719,7 +715,6 @@ GameObject:
- component: {fileID: 1556045507}
- component: {fileID: 1556045506}
- component: {fileID: 1556045505}
- component: {fileID: 1556045509}
m_Layer: 0
m_Name: Canvas
m_TagString: Untagged
@@ -812,18 +807,6 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!114 &1556045509
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1556045504}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a478e5f6126dc184ca902adfb35401b4, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &1584557231
GameObject:
m_ObjectHideFlags: 0

View File

@@ -145,6 +145,11 @@ namespace Cysharp.Threading.TasksTests
public int MyProperty { get; set; }
}
class MyBooleanClass
{
public bool MyProperty { get; set; }
}
[UnityTest]
public IEnumerator WaitUntil() => UniTask.ToCoroutine(async () =>
{
@@ -159,6 +164,20 @@ namespace Cysharp.Threading.TasksTests
diff.Should().Be(11);
});
[UnityTest]
public IEnumerator WaitUntilWithState() => UniTask.ToCoroutine(async () =>
{
var v = new MyBooleanClass { MyProperty = false };
UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(() => v.MyProperty = true).Forget();
var startFrame = Time.frameCount;
await UniTask.WaitUntil(v, static v => v.MyProperty, PlayerLoopTiming.EarlyUpdate);
var diff = Time.frameCount - startFrame;
diff.Should().Be(11);
});
[UnityTest]
public IEnumerator WaitWhile() => UniTask.ToCoroutine(async () =>
{
@@ -173,6 +192,20 @@ namespace Cysharp.Threading.TasksTests
diff.Should().Be(11);
});
[UnityTest]
public IEnumerator WaitWhileWithState() => UniTask.ToCoroutine(async () =>
{
var v = new MyBooleanClass { MyProperty = true };
UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(() => v.MyProperty = false).Forget();
var startFrame = Time.frameCount;
await UniTask.WaitWhile(v, static v => v.MyProperty, PlayerLoopTiming.EarlyUpdate);
var diff = Time.frameCount - startFrame;
diff.Should().Be(11);
});
[UnityTest]
public IEnumerator WaitUntilValueChanged() => UniTask.ToCoroutine(async () =>
{

View File

@@ -1,4 +1,6 @@
using Cysharp.Threading.Tasks;
#pragma warning disable CS0618
using Cysharp.Threading.Tasks;
using Cysharp.Threading.Tasks.Linq;
using FluentAssertions;
using NUnit.Framework;

View File

@@ -1,4 +1,6 @@
#if !(UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2)
#pragma warning disable CS0618
#if !(UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2)
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using UnityEngine;