C# 在异步函数初始化静态属性之前防止对其进行访问
我有一个函数,可以异步加载xml文件,解析它,并将某些值添加到列表中。我正在使用async并等待它。我遇到的问题是,在调用wait之后,在异步函数完成添加所有项之前,程序将继续执行访问该列表的代码 我的带有异步函数的静态类:C# 在异步函数初始化静态属性之前防止对其进行访问,c#,unity3d,asynchronous,C#,Unity3d,Asynchronous,我有一个函数,可以异步加载xml文件,解析它,并将某些值添加到列表中。我正在使用async并等待它。我遇到的问题是,在调用wait之后,在异步函数完成添加所有项之前,程序将继续执行访问该列表的代码 我的带有异步函数的静态类: using System; using System.Threading.Tasks; using System.Collections.Generic; using System.Xml.Linq; using UnityEngine; using UnityEngin
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Xml.Linq;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.AddressableAssets;
namespace Drok.Localization
{
public static class Localization
{
/// <summary>
/// The currently available languages.
/// </summary>
public static List<string> Available { get; private set; } = new List<string>();
/// <summary>
/// The currently selected language.
/// </summary>
public static string Current { get; private set; } = null;
public static async Task Initialize()
{
await LoadMetaData();
}
private static async Task LoadMetaData()
{
AsyncOperationHandle<TextAsset> handle = Addressables.LoadAssetAsync<TextAsset>("Localization/meta.xml");
TextAsset metaDataFile = await handle.Task;
XDocument metaXMLData = XDocument.Parse(metaDataFile.text);
IEnumerable<XElement> elements = metaXMLData.Element("LangMeta").Elements();
foreach (XElement e in elements)
{
string lang = e.Attribute("lang").Value;
int id = Int32.Parse(e.Attribute("id").Value);
Debug.LogFormat("Language {0} is availible with id {1}.", lang, id);
Available.Add(lang);
}
}
public static void LoadLanguage(string lang)
{
Current = lang;
throw new NotImplementedException();
}
public static string GetString(string key)
{
return key;
}
}
}
在添加所有项目之前,我不知道如何阻止访问该列表。我发布的代码只是收集关于可用语言的信息,以便以后只能加载使用的一种语言。扩展Rufus的评论: 声明一个初始化为
false
的bool
属性。在列表的getter中,仅当所述bool
属性为true
时才返回列表,如果false
,则可能返回null
(这取决于您的要求)
public static bool IsAvailable { get; set; } = false;
private static List<string> _available;
public static List<string> Available
{
get
{
if (IsAvailable)
return _available;
else
return null;
}
set { _available = value; }
}
publicstaticboolisavailable{get;set;}=false;
私有静态列表可用;
公共静态列表可用
{
得到
{
如果(可用)
返回可用;
其他的
返回null;
}
设置{u available=value;}
}
最后,在
async
函数中,当工作完成时,将上述属性设置为true。一种可能是添加一些逻辑,以便在从get
访问器返回列表时等待加载元数据
一种方法是在列表准备就绪时将bool
字段设置为true
,然后根据bool
字段的值返回私有备份list
或null
:
public static class Localization
{
private static bool metadataLoaded = false;
private static List<string> available = new List<string>();
// The 'Available' property returns null until the private list is ready
public static List<string> Available => metadataLoaded ? available : null;
private static async Task LoadMetaData()
{
// Add items to private 'available' list here
// When the list is ready, set our field to 'true'
metadataLoaded = true;
}
}
公共静态类本地化
{
私有静态bool metadataload=false;
私有静态列表可用=新列表();
//在私有列表准备就绪之前,“Available”属性将返回null
公共静态列表可用=>metadataLoaded?可用:null;
私有静态异步任务LoadMetaData()
{
//在此处将项目添加到私人“可用”列表
//列表准备好后,将字段设置为“true”
metadataLoaded=true;
}
}
Awake方法是async void,因此调用者无法保证它在转到其他方法之前完成
但是,您可以保留任务并在Start
方法中等待它,以确保任务已完成。等待两次并没有什么坏处
public class LanguageMenu : MonoBehaviour
{
private Task _task;
private async void Awake()
{
_task = Localization.Initialize();
await _task;
}
private async void Start()
{
await _task;
Debug.Log(Localization.Available.Count);
}
private void Update()
{
}
}
任务
表示将来将确定的某个值(类型为T
)。如果将属性设置为此类型,则它将强制所有调用者等待加载该属性:
public static class Localization
{
public static Task<List<string>> Available { get; private set; }
static Localization() => Available = LoadMetaDataAsync();
private static async Task<List<string>> LoadMetaDataAsync()
{
var results = new List<string>();
...
results.Add(lang);
return results;
}
}
最新,当涉及
Update
方法时,使用async
和await
等待其执行可能还不够
通常,对于Unity消息,除了使用async
之外,还有一个大的选择:事件系统,例如
public static class Localization
{
public static event Action OnLocalizationReady;
public static async void Initialize()
{
await LoadMetaData();
OnLocalizationReady?.Invoke();
}
...
}
public class LanguageMenu : MonoBehaviour
{
private bool locaIsReady;
private void Awake()
{
Localization.OnLocalizationReady -= OnLocalizationReady;
Localization.OnLocalizationReady += OnLocalizationReady;
Localization.Initialize();
}
private void OnDestroy ()
{
Localization.OnLocalizationReady -= OnLocalizationReady;
}
// This now replaces whatever you wanted to do in Start originally
private void OnLocalizationReady ()
{
locaIsReady = true;
Debug.Log(Localization.Available.Count);
}
private void Update()
{
// Block execution until locaIsReady
if(!locaIsReady) return;
...
}
}
public static class Localization
{
public static event Action<List<string>> OnLocalizationReady;
...
}
并在任何类中使用它来等待该事件,例如
public static class Localization
{
public static event Action OnLocalizationReady;
public static async void Initialize()
{
await LoadMetaData();
OnLocalizationReady?.Invoke();
}
...
}
public class LanguageMenu : MonoBehaviour
{
private bool locaIsReady;
private void Awake()
{
Localization.OnLocalizationReady -= OnLocalizationReady;
Localization.OnLocalizationReady += OnLocalizationReady;
Localization.Initialize();
}
private void OnDestroy ()
{
Localization.OnLocalizationReady -= OnLocalizationReady;
}
// This now replaces whatever you wanted to do in Start originally
private void OnLocalizationReady ()
{
locaIsReady = true;
Debug.Log(Localization.Available.Count);
}
private void Update()
{
// Block execution until locaIsReady
if(!locaIsReady) return;
...
}
}
public static class Localization
{
public static event Action<List<string>> OnLocalizationReady;
...
}
或者,为了获得更好的性能,您还可以在Awake
中设置enabled=false
,然后在OnLocalizationReady
中将其设置为true,这样您就可以去掉locai就绪
标志
不需要
async
和wait
如果要移动
Localization.Initialize()
改为Start
您将给其他类机会在本地化之前添加一些回调。OnLocalizationReady
在唤醒中(
;)
您可以通过多种方式扩展此功能!例如,您可以与直接触发事件一起传递对可用的
的引用,以便侦听器可以像这样直接使用它
public static class Localization
{
public static event Action OnLocalizationReady;
public static async void Initialize()
{
await LoadMetaData();
OnLocalizationReady?.Invoke();
}
...
}
public class LanguageMenu : MonoBehaviour
{
private bool locaIsReady;
private void Awake()
{
Localization.OnLocalizationReady -= OnLocalizationReady;
Localization.OnLocalizationReady += OnLocalizationReady;
Localization.Initialize();
}
private void OnDestroy ()
{
Localization.OnLocalizationReady -= OnLocalizationReady;
}
// This now replaces whatever you wanted to do in Start originally
private void OnLocalizationReady ()
{
locaIsReady = true;
Debug.Log(Localization.Available.Count);
}
private void Update()
{
// Block execution until locaIsReady
if(!locaIsReady) return;
...
}
}
public static class Localization
{
public static event Action<List<string>> OnLocalizationReady;
...
}
如果无论如何,LanguageMenu
将是唯一的侦听器,那么您甚至可以将回调作为参数直接传递给Initialize
等
public static class Localization
{
public static async void Initialize(Action<List<string>> onSuccess)
{
await LoadMetaData();
onSuccess?.Invoke();
}
...
}
更新
至于您关于稍后初始化的问题:是的,还有一个简单的修复程序
public static class Localization
{
public static event Action OnLocalizationReady;
public static bool isInitialized;
public static async void Initialize()
{
await LoadMetaData();
isInitialized = true;
OnLocalizationReady?.Invoke();
}
...
}
然后在其他类中,您可以使用回调或立即初始化:
private void Awake()
{
if(Localization.isInitialized)
{
OnLocaInitialized();
}
else
{
Localization.OnInitialized -= OnLocaInitialized;
Localization.OnInitialized += OnLocaInitialized;
}
}
private void OnDestroy ()
{
Localization.OnInitialized -= OnLocaInitialized;
}
private void OnLocaInitialized()
{
var available = Localization.Available;
...
}
private void Update()
{
if(!Localization.isInitialized) return;
...
}
在get
访问器中添加一些逻辑,以确保在返回列表之前完成初始化?请参阅,我曾考虑过这样做,但列表和其他稍后将初始化的内容将在许多不同的位置访问,因此不停检查是否已完成似乎是不合理的,否?是否有一种方法可以将加载时的异步调用视为同步调用?或者是一种仅在需要时将文件加载到内存中的替代方法,这不是异步的。如果有帮助的话,我在原始帖子中对我的用例进行了扩展。当然,从所有这些方法中删除async
,并让它们同步运行。为什么要首先添加async
?如果内存可用,如果不等待加载,则会出现null指针异常。编辑:是的,只是检查了一下,如果我把所有的方法都改成非异步的,然后说TextAsset metaDataFile=handle.Result;当我试图解析它时,会得到一个空指针。我假设这是因为load方法仍然是异步的,因为我无法更改它。您是否尝试过替代LoadAssetAsync
?这似乎更符合我的需要,但我看到的主要问题是我得到的是null而不是正确的值。是否有一种方法可以告诉它等待完成,而不是只返回null?公共静态列表可用{get{while(!metadataLoaded);return Available;}}}
这将等待元数据加载,但可能会导致在客户端等待时挂起。例如,如果他们在调用Initialize
之前尝试访问列表,他们将永远等待。Hmmm这确实会导致挂起,即使我调用LoadMetaData b