Unity 之封装使用Async、Await语法实现异步线程

Unity 使用Async、Await语法实现异步线程


      我们都知道Unity是单线程,哪怕后面高版本渲染这块移动到了其他线程。

      Async、Await是C# 5.0的新加的语法,是个非常优雅的语法糖。

      我们看看异步函数的声明:

async void SyncFunctionTest()
{
}

      很简洁明了,就一个async关键字。虽然被标记异步关键字,但是,还是在当前线程执行。最后我们答应下线程的ID,证明我之前的说法是正确的,代码是这样的:

void Start()
{
	Debug.Log(Thread.CurrentThread.ManagedThreadId);
	SyncFunctionTest();
}

async void SyncFunctionTest()
{
	Debug.Log(Thread.CurrentThread.ManagedThreadId);
}

      我们看看控制台:
在这里插入图片描述
      好了,进入主题,实现一个线程的切换!那么在Unity中我们如何优化的切换一个线程呢?线程池?new Thread? 不不不!

      我们看下代码:

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

namespace EngineAsyncUtility
{
    public partial class AsyncUtility : MonoBehaviour
    {
        public static AsyncUtility Instance;

        private static int UnityThreadID { get; set; }
        public static bool InUnityThreadContext => Thread.CurrentThread.ManagedThreadId == UnityThreadID;
        public static SynchronizationContext UnitySyncContext { get; private set; }
        public static SynchronizationContext BackgroundSyncContext { get; private set; }

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        public static void AsyncUtilityInitlize()
        {
            UnityThreadID = Thread.CurrentThread.ManagedThreadId;
            UnitySyncContext = SynchronizationContext.Current;
            BackgroundSyncContext = new SynchronizationContext();
            
            Instance = new GameObject("AsyncUtility").AddComponent<AsyncUtility>();
            UnityEngine.Object.DontDestroyOnLoad(Instance);
        }
    }
}

      SynchronizationContext ? 好陌生,这个是个什么鬼?用个通俗的大白话来说就是一个线程间上下文调度对象。那么它是如何完成一个线程间的上下文调度?我们看看源码:

    /// <summary>When overridden in a derived class, dispatches a synchronous message to a synchronization context.</summary>
    /// <param name="d">The <see cref="T:System.Threading.SendOrPostCallback" /> delegate to call.</param>
    /// <param name="state">The object passed to the delegate.</param>
    /// <exception cref="T:System.NotSupportedException">The method was called in a Windows Store app. The implementation of <see cref="T:System.Threading.SynchronizationContext" /> for Windows Store apps does not support the <see cref="M:System.Threading.SynchronizationContext.Send(System.Threading.SendOrPostCallback,System.Object)" /> method.</exception>
    [__DynamicallyInvokable]
    public virtual void Send(SendOrPostCallback d, object state)
    {
      d(state);
    }

    /// <summary>When overridden in a derived class, dispatches an asynchronous message to a synchronization context.</summary>
    /// <param name="d">The <see cref="T:System.Threading.SendOrPostCallback" /> delegate to call.</param>
    /// <param name="state">The object passed to the delegate.</param>
    [__DynamicallyInvokable]
    public virtual void Post(SendOrPostCallback d, object state)
    {
      ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state);
    }

      我只截取了部分代码,也是上下文调度的主要函数Send和Post,从明面上看,Send是在当前线程完成的调用,Post是从线程池取出来调用。

      另外,最终被执行到Thread.cs中的GetExecutionContextReader函数中来切换上下文线程,(中间部分函数我没追踪到。)

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    internal ExecutionContext.Reader GetExecutionContextReader()
    {
      return new ExecutionContext.Reader(this.m_ExecutionContext);
    }

      其实最重要还是 send和post,你提交的方式决定了是否是在当前线程来提交。

       AsyncUtility .cs大致就说到这里,那么我们如何使用Async和Await呢?

       首先实现一个 SynchronizationContextAwaiter 有过Async和Await基本理解的都知道如果我们要自定义Async/Await,就要自行实现一个Awaiter并继承INotifyCompletion。

       最后我实现的源码是这样的:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using UnityEngine;

namespace EngineAsyncUtility.Awaiters
{
    public struct SynchronizationContextAwaiter : INotifyCompletion
    {
        private static readonly SendOrPostCallback _postCallback = state => ((Action)state)();
        private readonly SynchronizationContext _context;
        
        public SynchronizationContextAwaiter(SynchronizationContext context)
        {
            this._context = context;
        }

        public bool IsCompleted => _context == SynchronizationContext.Current;

        public void OnCompleted(Action continuation)
        {
            _context.Post(_postCallback, continuation);
        }

        public void GetResult() { }
    }
}

       最后我们扩展下SynchronizationContext类:

namespace EngineAsyncUtility.Extensions
{
    public static class Extensions
    {
        public static SynchronizationContextAwaiter GetAwaiter(this SynchronizationContext scontext)
            => new SynchronizationContextAwaiter(scontext);
    }
}

       OK!基本大致就是这样,非常简单!我们写个测试代码验证下结果:

    void Start()
    {
        SyncContextTest();
    }
    async void SyncContextTest()
    {
        Debug.Log(AsyncUtility.InUnityThreadContext);
        Debug.Log("Unity Thread true");
        await AsyncUtility.BackgroundSyncContext;
        Debug.Log(AsyncUtility.InUnityThreadContext);
        Debug.Log("Unity Thread false");
        await AsyncUtility.UnitySyncContext; 
        Debug.Log(AsyncUtility.InUnityThreadContext);
        Debug.Log("Unity Thread true");
    }

      结果很明显了。
在这里插入图片描述

      最后我们做个极端的测试,我们都知道,如果我们在其他线程操作UI,那么Unity就会给你throw一个error!我们试下是否真的切换到其他线程了!测试代码:

    public Text t1;
    public Text t2;
    public Text t3;
    void Start()
    {
        SyncContextTest();
    }
    async void SyncContextTest()
    {
        Debug.Log(AsyncUtility.InUnityThreadContext);
        Debug.Log("Unity Thread true");
        t1.text = "Unity Thread true";
        await AsyncUtility.BackgroundSyncContext;
        Debug.Log(AsyncUtility.InUnityThreadContext);
        Debug.Log("Unity Thread false");
        try
        {
            t2.text = "Unity Thread false";
        }
        catch (Exception e)
        {
           Debug.LogError(e);
        }
        await AsyncUtility.UnitySyncContext; 
        Debug.Log(AsyncUtility.InUnityThreadContext);
        Debug.Log("Unity Thread true");
        t3.text = "Unity Thread false";
    }

在这里插入图片描述
      干得漂亮小老弟,那么我们基于做个逻辑就可以实现很多种效果,包括Unity的协程。

最后奉献上代码:code  qf05

  • 8
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值