unity使用async await異步unitywebrequest 加載 streamingAssets文件,取代 WWW 和 協程


如果現在你在中文網上查詢一些關於加載 streamingAssetsPath 或者是 persistentDataPath 路徑下的文件,你能找到的大部分都是讓你用 WWW 配合 協程 來實現這一步。

中文網上此類相關介紹很少。WWW 和 協程 的編寫讓代碼變得臃腫,而協程連返回值都沒。要么用大段代碼塊,或者是注入委托調用。

unity現在已經支持 .net core 了,並且引入UnityWebRequest

 

 

 

 

 

先上代碼,加載本地的圖片文件

 
         
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public static class LoadHelper
{
public static async Task<Sprite> LoadSpritePNG(string path)
{
var task = LoadAsyncSprite(path, ".png");
Task<Sprite> t = await Task.WhenAny(task);
return t.Result;
}

public static async Task<Sprite> LoadSpriteJPG(string path)
{
var task = LoadAsyncSprite(path, ".jpg");
Task<Sprite> t = await Task.WhenAny(task);
return t.Result;
}

static async Task<Sprite> LoadAsyncSprite(string url, string end)
{
string path = Path.Combine(Application.streamingAssetsPath, url + end);
var getRequest = UnityWebRequest.Get(path);
await getRequest.SendWebRequest();
byte[] imgData = getRequest.downloadHandler.data;
Texture2D tex = new Texture2D(2, 2);
tex.LoadImage(imgData);
Vector2 pivot = new Vector2(0.5f, 0.5f);
Sprite sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), pivot, 100.0f);
return sprite;
}

}

public static class ExtensionMethods
{
public static TaskAwaiter GetAwaiter(this AsyncOperation asyncOp)
{
var tcs = new TaskCompletionSource<object>();
asyncOp.completed += obj => { tcs.SetResult(null); };
return ((Task)tcs.Task).GetAwaiter();
}
}
 

調用即可

 

 

 

 

 

 

---------------------------------------------------------------------------------------分割--------------------------------------------------------------------------------------------------------

 

好的那么下面來講下async await

讓我們看一個簡單的例子。給定以下協程:

public class AsyncExample : MonoBehaviour
{
    IEnumerator Start()
    {
        Debug.Log("Waiting 1 second...");
        yield return new WaitForSeconds(1.0f);
        Debug.Log("Done!");
    }
}

使用async-await的等效方法如下:

public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        Debug.Log("Waiting 1 second...");
        await Task.Delay(TimeSpan.FromSeconds(1));
        Debug.Log("Done!");
    }
}

簡而言之,Unity協程使用C#對迭代器塊的內置支持來實現您提供給StartCoroutine方法的IEnumerator迭代器對象由Unity保存,並且該迭代器對象的每一幀都向前推進以獲取由協程產生的新值。然后,Unity會讀取您“ yield return”的不同值以觸發特殊情況的行為,例如執行嵌套協程(返回另一個IEnumerator時),延遲幾秒鍾(返回WaitForSeconds類型的實例時)或等到下一幀(返回null時)。

不幸的是,由於async-await在Unity中是相當新的事實,因此上述對協程的內置支持並不像async-await那樣以類似的方式存在。這意味着我們必須自己添加很多這種支持。

但是,Unity確實為我們提供了一個重要方面。如您在上面的示例中看到的,默認情況下,我們的異步方法將在主unity線程上運行。在非統一C#應用程序中,異步方法通常會自動在單獨的線程上運行,這在Unity中會是一個大問題,因為在這種情況下我們將無法始終與Unity API進行交互。沒有Unity引擎的支持,我們對異步方法內部的Unity方法/對象的調用有時會失敗,因為它們將在單獨的線程上執行。在后台,它可以這樣工作,因為Unity提供了一個默認的SynchronizationContext,稱為UnitySynchronizationContext,它自動收集在每個幀中排隊的所有異步代碼,並繼續在主要unity線程上運行它們。

事實證明,這足以讓我們開始使用async-await!我們只需要一些幫助程序代碼就可以使我們做一些有趣的事情,而不僅僅是簡單的時間延遲。

 

當前,我們沒有很多有趣的異步代碼可以編寫。我們可以調用其他異步方法,並且可以使用Task.Delay,就像上面的示例中一樣,但除此之外就不多了。

作為一個簡單的示例,讓我們添加直接在TimeSpan上“等待”的功能,而不必總是像上面的示例那樣每次都調用Task.Delay。像這樣:

public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        await TimeSpan.FromSeconds(1);
    }
}

我們需要做的就是為此添加一個自定義的GetAwaiter擴展方法到TimeSpan類中:

public static class AwaitExtensions
{
    public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)
    {
        return Task.Delay(timeSpan).GetAwaiter();
    }
}

之所以可行,是因為為了在更新版本的C#中支持“等待”給定的對象,所需要做的就是該對象具有一個名為GetAwaiter的方法,該方法返回一個Awaiter對象。這很棒,因為它允許我們通過使用上述擴展方法來等待我們想要的任何東西,而無需更改實際的TimeSpan類。

我們也可以使用這種方法來支持等待其他類型的對象,包括Unity用於協程指令的所有類!我們可以使WaitForSeconds,WaitForFixedUpdate,WWW等全部等待,就像它們在協程中可屈服一樣。我們還可以向IEnumerator添加GetAwaiter方法以支持等待協程,以允許將異步代碼與舊的IEnumerator代碼互換。

 

------------------------------------------------------------------------------------------------------------------------------

此處已經有一些工具類,定義好了一些GetAwaiter

https://github.com/svermeulen/Unity3dAsyncAwaitUtil/releases

【使用】

public class AsyncExample : MonoBehaviour
{
    public async void Start()
    {
        // Wait one second
        await new WaitForSeconds(1.0f);
 
        // Wait for IEnumerator to complete
        await CustomCoroutineAsync();
 
        await LoadModelAsync();
 
        // You can also get the final yielded value from the coroutine
        var value = (string)(await CustomCoroutineWithReturnValue());
        // value is equal to "asdf" here
 
        // Open notepad and wait for the user to exit
        var returnCode = await Process.Start("notepad.exe");
 
        // Load another scene and wait for it to finish loading
        await SceneManager.LoadSceneAsync("scene2");
    }
 
    async Task LoadModelAsync()
    {
        var assetBundle = await GetAssetBundle("www.my-server.com/myfile");
        var prefab = await assetBundle.LoadAssetAsync<GameObject>("myasset");
        GameObject.Instantiate(prefab);
        assetBundle.Unload(false);
    }
 
    async Task<AssetBundle> GetAssetBundle(string url)
    {
        return (await new WWW(url)).assetBundle
    }
 
    IEnumerator CustomCoroutineAsync()
    {
        yield return new WaitForSeconds(1.0f);
    }
 
    IEnumerator CustomCoroutineWithReturnValue()
    {
        yield return new WaitForSeconds(1.0f);
        yield return "asdf";
    }
}

如您所見,像這樣使用async await可能非常強大,尤其是當您像上面的LoadModelAsync方法中一樣開始組合多個異步方法時。

請注意,對於返回值的異步方法,我們使用Task的通用版本,並將返回類型作為通用參數傳遞,就像上面的GetAssetBundle一樣。

還要注意,在大多數情況下,使用上面的WaitForSeconds實際上比我們的TimeSpan擴展方法更可取,因為WaitForSeconds將使用Unity游戲時間,而我們的TimeSpan擴展方法將始終使用實時(因此不受Time.timeScale更改的影響)

(摘自:http://www.stevevermeulen.com/index.php/blog/)

 

 視頻地址  https://gametorrahod.com/unity-and-async-await/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM