Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/date/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在异步函数初始化静态属性之前防止对其进行访问_C#_Unity3d_Asynchronous - Fatal编程技术网

C# 在异步函数初始化静态属性之前防止对其进行访问

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

我有一个函数,可以异步加载xml文件,解析它,并将某些值添加到列表中。我正在使用async并等待它。我遇到的问题是,在调用wait之后,在异步函数完成添加所有项之前,程序将继续执行访问该列表的代码

我的带有异步函数的静态类:

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