【C#】并行编程实战:异步编程详解

        在前面章节中,我们已经看到了并行编程的工作方式:创建称为工作单元(Unit of Work)的小任务,这些任务可以由一个或多个线程同时执行。

        本章将从介绍同步代码和异步代码之间的区别开始,然后讨论何时适合使用异步代码,以及何时应避免异步代码。最后我们将讨论并行编程中的新功能以解决异步代码复杂性的帮助。

        本教程对应学习工程:魔术师Dix / HandsOnParallelProgramming · GitCode

        本章相当于阶段性总结,新知识不多,总体较为简单。


1、程序执行的类型

        书上这里讲了同步操作和异步操作的执行顺序,我觉得是属于偏新手向的,不需要额外说明。同步执行就是代码只能一行一行、从上往下执行;异步的话就会同时执行多个代码块。相信这点基础知识不需要赘述大家都能明白,理解同步与异步并不难。

        这里直接跳过,下一部分。

2、适合使用异步编程的情形

        何时适合使用异步编程,微软有个说明文档:

同步和异步操作 - WCF | Microsoft Learn了解实现和调用异步服务操作。 WCF 服务和客户端可以在应用程序的两个级别使用异步操作。icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/framework/wcf/synchronous-and-asynchronous-operations

        里面建议了以下情形:

  • 如果从中间层应用程序调用操作。

  • 果在 ASP.NET 页中调用操作,可使用异步页。

  • 如果从任何单线程的应用程序(如 Windows 窗体或 Windows Presentation Foundation (WPF))调用操作。 使用基于事件的异步调用模型时,将在 UI 线程上引发结果事件,从而向应用程序添加响应性而无需您自己处理多个线程。

        粗粗看了一下,跟我们 Unity 开发一点关系都没有。而且对于 Unity 开发,何时使用异步多线程一直有较为明确的场景:网络、IO、大量数据的处理等。总之,出于对性能的考虑,自然是当某任务放在主线程会造成卡顿时,就要考虑是否要异步多线程。

3、编写异步代码

        有以下实现异步的方式:

  • 使用 Delegate.BeginInvoke 方法;

  • 使用 Task 类;

  • 使用 IAsyncResult 接口;

  • 使用 async 和 await 关键字。

3.1、使用 Delegate.BeginInvoke 方法

        这个是对于委托有这个方法,我们直接看一段代码:

        private void RunWithBegionInvoke()
        {
            Action logAction = AsyncLogAction;
            logAction.BeginInvoke(null, null);
            Debug.Log("RunWithBegionInvoke !!!");
        }
        
        public static async void AsyncLogAction()
        {
            await Task.Delay(2500);
            Debug.Log("AsyncLogAction Finish !!!");
        }

        我们直接执行,结果如下:

         可以看到,BeginInvoke 并没有阻塞主线程,而实现了异步的效果。同时,这个函数还支持回调和参数,可以说是非常好用了。他这个底层其实也是用 IAsyncResult 实现的,当然自动实现的会有一定的额外开销。

3.2、使用 Task 类

        这之前用太多了,不赘述了,可以参考第二章。略过。

3.3、使用 IAsyncResult 接口

IAsyncResult 接口 (System) | Microsoft Learn表示异步操作的状态。 icon-default.png?t=N6B9https://learn.microsoft.com/zh-cn/dotnet/api/system.iasyncresult?view=netstandard-2.1        这种接口语法的知识,直接上代码比较好理解。这里我们随便定义一个自己的 IAsyncResult,当然里面的参数我就随便写写:

    /// <summary>
    /// IAsyncResult 的示例代码;
    /// </summary>
    public class MyAsyncResult : IAsyncResult
    {
        public MyAsyncResult(MyAsyncState state)
        {
            m_AutoResetEvent = new AutoResetEvent(false);
            IsCompleted = false;
            m_AsyncState = state;
            CompletedSynchronously = false;
        }

        private MyAsyncState m_AsyncState;
        public object AsyncState => m_AsyncState;

        private AutoResetEvent m_AutoResetEvent;
        public WaitHandle AsyncWaitHandle => m_AutoResetEvent;

        public bool CompletedSynchronously { get; private set; }
        public bool IsCompleted { get; private set; }

    }
    
    /// <summary>
    ///  自定义的数据结构
    /// </summary>
    public class MyAsyncState
    {

        public Vector3 Positon;
        public Quaternion Rotation;

    }

        之后我们尝试调用他:

        private void RunWtihMyAsyncResult()
        {
            MyAsyncState state = new MyAsyncState();
            state.Positon = new Vector3(50, 811, 55);
            state.Rotation = Quaternion.Euler(7, 8, 9);
            MyAsyncResult result = new MyAsyncResult(state);

            Action logAction = AsyncLogAction;
            // OnActionCallBack 的回调仍然在子线程
            logAction.BeginInvoke(OnActionCallBack, result);

            Debug.Log($"RunWtihMyAsyncResult : {Task.CurrentId}");
        }
        
        public static void OnActionCallBack(IAsyncResult result)
        {
            Debug.Log($"OnActionCallBack : {result.GetType()} | {Task.CurrentId} !");

            MyAsyncResult myResult = result.AsyncState as MyAsyncResult;
            MyAsyncState myState = myResult.AsyncState as MyAsyncState;

            Debug.Log($"Positon : {myState.Positon}");
            Debug.Log($"EulerAngles : {myState.Rotation.eulerAngles}");
            Debug.Log($"IsCompleted : {result.IsCompleted}");
            Debug.Log($"CompletedSynchronously : {result.CompletedSynchronously}");
            Debug.Log($"OnActionCallBack : End  !");
        }

        可以看到,这里的回调 MyAsyncResult 是返回的 IAsyncResult 中的 AsyncState ,属于层层套娃了。系统返回的这个接口实现是 System.Runtime.Remoting.Messaging.AsyncResult 这个类,应该是自动封装的。

        注意:BeginInvoke 返回调用的 IAsyncResult 接口仍然是在子线程 !

        当然,上述写法发现,我们自己写的这个 IAsyncResult 几乎没有什么用……不过 C# 提供了一个通用的简便写法:

        private void RunWtihAsyncCallBack()
        {
            MyAsyncState state = new MyAsyncState();
            state.Positon = new Vector3(888, 831, 255);
            state.Rotation = Quaternion.Euler(76, 38,329);
            Action logAction = TestFunction.AsyncLogAction;
            logAction.BeginInvoke(TestFunction.OnActionCallBackSimple, state);
        }

        这里返回的同样也是 System.Runtime.Remoting.Messaging.AsyncResult 。和我预期不一样的是,回调的调用是比委托先执行的,所以暂时不知道这个接口有啥用。

4、不宜使用异步编程的情形

        这里作者提供了4种情形:

  • 没有连接池的单个数据库中

  • 更注重代码的易于阅读和维护

  • 操作简单且运行时间短

  • 应用程序使用了大量的共享资源

        其实对于我们 Untiy 开发来说,这些都不是问题。Unity 本来就推荐在主线程做完所有事情,多线程就是高级用法。写多线程的程序员一般都知道何时该使用多线程。Unity 由于禁用了很多接口在子线程调用,实际上大部分和 Untiy 相关的代码都只能在主线程,使用子线程的大部分是 IO 或者纯数据处理等。


5、本章小节

        本章其实算是总结吧,介绍了异步和同步的概念,还讨论了异步的各种实现。但我个人感觉干货其实并不多,有用的东西不如前面几章讲的,有点水。

        本教程对应学习工程:魔术师Dix / HandsOnParallelProgramming · GitCode

猜你喜欢

转载自blog.csdn.net/cyf649669121/article/details/131829094
今日推荐