为什么 Unity 在编辑器模式下使用 async/await 插件时会抛出 NullReferenceException?

G4b*_*i3l 1 c# unity-game-engine async-await

我试图在 Unity 编辑器窗口上显示一个简单的图像,它工作了一段时间,然后我想我做了一些破坏它的更改,我不断收到 NullReferenceException。这是我的简单示例:

public class ShowImagePreview : EditorWindow
{
  private Texture2D texture;

  private async Task<Texture2D> GetHTTPTextureAsync(string imageUrl)
  {
    UnityWebRequest request = UnityWebRequestTexture.GetTexture(imageUrl);
    await request.SendWebRequest();

    if (request.isNetworkError || request.isHttpError)
    {
      Debug.Log("Something went wrong!");
      Debug.Log(request.error);
      return null;
    }

    return ((DownloadHandlerTexture)request.downloadHandler).texture;
  }

  private async Task OnGUI()
  {
    GUILayout.BeginHorizontal("box");
    if (texture != null)
    {
      GUILayout.Label("Texture is not null!");
      GUILayout.Box(texture, GUILayout.Width(140), GUILayout.Height(100));
    }
    else
    {
      GUILayout.Label("Texture is NULL!");
      try
      {
        // This is what's causing the issue and keeping the texture value to null
        texture = await GetHTTPTextureAsync("https://nikkorpon.files.wordpress.com/2011/02/random-shots-059.jpg");
      }
      catch (Exception e)
      {
        Debug.Log(e);
      }
    }
    GUILayout.EndHorizontal();
  }
}
Run Code Online (Sandbox Code Playgroud)

这是从一个更大的脚本中摘录的。图片 url 实际上是动态的,但为了这个例子,我只是抓取了一个随机的图片 url。我正在使用 Async/Await 插件,它可以按预期处理任何其他请求。知道我可能做错了什么吗?

这是完整的错误消息:

System.NullReferenceException: Object reference not set to an instance of an object
  at IEnumeratorAwaitExtensions.RunOnUnityScheduler (System.Action action) [0x00042] in /Users/user_name/Projects/project_name/Assets/Plugins/AsyncAwaitUtil/Source/IEnumeratorAwaitExtensions.cs:128 
  at IEnumeratorAwaitExtensions.GetAwaiterReturnSelf[T] (T instruction) [0x00025] in /Users/user_name/Projects/project_name/Assets/Plugins/AsyncAwaitUtil/Source/IEnumeratorAwaitExtensions.cs:115 
  at IEnumeratorAwaitExtensions.GetAwaiter (UnityEngine.AsyncOperation instruction) [0x00002] in /Users/user_name/Projects/project_name/Assets/Plugins/AsyncAwaitUtil/Source/IEnumeratorAwaitExtensions.cs:55 
  at ShowImagePreview+<GetHTTPTextureAsync>c__async0.MoveNext () [0x00033] in /Users/user_name/Projects/project_name/Assets/Resources/Scripts/Import.cs:18 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <f2e6809acb14476a81f399aeb800f8f2>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <f2e6809acb14476a81f399aeb800f8f2>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <f2e6809acb14476a81f399aeb800f8f2>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <f2e6809acb14476a81f399aeb800f8f2>:0 
  at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <f2e6809acb14476a81f399aeb800f8f2>:0 
  at ShowImagePreview+<OnGUI>c__async1.MoveNext () [0x0010b] in /Users/user_name/Projects/project_name/Assets/Resources/Scripts/Import.cs:43 
UnityEngine.Debug:Log(Object)
<OnGUI>c__async1:MoveNext() (at Assets/Resources/Scripts/Import.cs:47)
System.Runtime.CompilerServices.AsyncTaskMethodBuilder:Start(<OnGUI>c__async1&)
ShowImagePreview:OnGUI()
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
Run Code Online (Sandbox Code Playgroud)

G4b*_*i3l 5

找到罪魁祸首了!

代码本身工作得很好,没有错误。主要问题(感谢@KevinGosse 的一些挖掘)是由 Async/Await 插件代码触发的。

一旦我将调试器放在IEnumeratorAwaitExtensions.cs 的第 128 行,我注意到它SyncContextUtil.UnitySynchronizationContext为空,这就是导致NullReference错误的原因。

SyncContextUtil.UnitySynchronizationContext是在initilized SyncContextUtil.cs脚本文件归功于Install方法。所以问题是,为什么这个方法没有被触发?

查看源代码,您可以看到装饰器[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)],它Install在触发播放模式时简单地调用该方法!但是我在编辑器模式下工作,因此无论我做什么,该Install方法都不会触发并且SyncContextUtil.UnitySynchronizationContext仍然null存在。

原来这个问题在 repo 中是已知的,你可以在这里查看。他们针对这个确切的问题提出了一个解决方案,以允许在编辑器模式下使用 Async/Await。

对我来说,它为什么在某些时候起作用非常令人费解,然后我又没有对代码进行任何更改。原因是我激活了播放模式一秒钟,然后停止了它并继续在编辑器模式下工作。正如我们在启动播放模式之前提到的,会调用Install使一切正常工作的方法。然后我关闭了 Unity,重新启动它并且(没有激活播放模式)我的脚本都不再工作了!

我现在将按照问题评论中建议的方法使 Async/Await 在编辑器模式下也按预期工作。我希望这个答案也能帮助其他人!

编辑

我向该Install方法添加了一个装饰器,并且不再出现错误。这是修改后的代码:

namespace UnityAsyncAwaitUtil
{
    public static class SyncContextUtil
    {
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        [InitializeOnLoadMethod]
        static void Install()
        {
            // As suggested by @derHugo you can add a null check here
            // which will prevent initializing these parameters twice
            // see his comment for more info
            if (UnitySynchronizationContext == null) {
              UnitySynchronizationContext = SynchronizationContext.Current;
            }
            if (UnityThreadId == null) {
              UnityThreadId = Thread.CurrentThread.ManagedThreadId;
            }
        }

        public static int UnityThreadId
        {
            get; private set;
        }

        public static SynchronizationContext UnitySynchronizationContext
        {
            get; private set;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经针对在编辑器模式下使用 Async/Await 进行了测试,因此请运行您自己的测试以确保此更改也适用于您的情况。

  • 您可能想要删除“RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)”或添加检查。我不确定使用多个属性时如何处理这些,但我猜它可能会在运行时被调用两次(尽管仅在编辑器中),因为进入播放模式时也会调用“InitializeOnLoadMethod” (2认同)