性能测试监控指标及分析调优 | 京东云技术团队一、哪些因素会成为系统的瓶颈?

2023-09-22 09:45:00

1. 什么是MAF和MEF?

MEF和MEF微软官方介绍:Managed Extensibility Framework (MEF) - .NET Framework | Microsoft Learn

MEF是轻量化的插件框架,MAF是复杂的插件框架。

因为MAF有进程隔离和程序域隔离可选。我需要插件进程隔离同时快速传递数据,最后选择了MAF。

如果不需要真正的物理隔离还是建议使用简单一点的MEF框架。

2.什么情况下会使用MAF框架?

例如想做一个浏览器,每点开一个页面都是一个独立的进程,然后进程之间相互隔离。或者多个不同的进程到同一个界面渲染,实现后台进程和界面之间的隔离。

多进程之间相互传递数据也可以使用MAF。

3. 如何学习MAF?

MAF其实是一项很老的技术,入门我看的是《WPF编程宝典》第32章 插件模型。里面有MAF和MEF的详细介绍和许多样例。

但是要深入理解还是看了很多其他的东西,下面我详细说明,我自己理解和总结的MAF。

4. MAF框架入门

4.1 MAF框架构成与搭建

MAF框架模式是固定的,这里做一个详细介绍。

首先是要添加几个新项目,下图中不包含主项目。

Addin文件夹是放置插件用的,其余都是必要项目。

假设HostView项目和主项目的输出路径是..\Output\

然后修改每个项目的输出文件夹,例如AddInSideAdapter项目输出路径可以设置为..\Output\AddInSideAdapters\

注意插件项目输出到Addin文件夹中的子文件夹是..\..\Output\AddIns\MyAddin\

最后项目的输出文件夹结构是:

D:\Demo\Output\AddIns

D:\Demo\Output\AddInSideAdapters

D:\Demo\Output\AddInViews

D:\Demo\Output\Contracts

D:\Demo\Output\HostSideAdapters

来看看MAF框架模型构成。

 上图中绿色的是被引用蓝色项目所引用。例如HostSideAdapter就要引用Contract和Hostview,如下图所示。

 注意引用时取消勾选复制本地。

 这时就完成基本项目结构的搭建。

4.2 MAF框架实现

这里想实现宿主项目和插件项目的双向通信。即插件项目将相关函数接口在宿主实现,然后将宿主项目相关函数接口用委托类的方式注册给插件项目。实现双向通信。

用《WPF编程宝典》样例代码来说,样例中,插件程序实现ProcessImageBytes处理图像数据的函数,处理同时需要向宿主项目报告处理进度,宿主中 ReportProgress函数实现进度可视化。

MAF实现一般是先写Contract协议,明确需要的函数接口。然后写AddlnView和HostView。实际上这两个是将函数接口抽象化,在接口里函数复制过来前面加 public abstract 就行。

之后HostSideAdapter和AddInSideAdapter直接快速实现接口。

首先从Contract开始,Contract是定义接口,需要设置对象标识符[AddInContract],且必须继承IContract。

 [AddInContract]
    public interface IImageProcessorContract : IContract
    {
        byte[] ProcessImageBytes(byte[] pixels);

        void Initialize(IHostObjectContract hostObj);
    }

    public interface IHostObjectContract : IContract
    {
        void ReportProgress(int progressPercent);
    }

Initialize函数是提供宿主函数注册的接口。

 然后在HostView和AddInView分别定义主程序和插件程序的接口抽象类。

public abstract class ImageProcessorHostView
    {
        public abstract byte[] ProcessImageBytes(byte[] pixels);

        public abstract void Initialize(HostObject host);
    }

    public abstract class HostObject
    {
        public abstract void ReportProgress(int progressPercent);
    }

注意AddlnView需要设置对象标识符[AddInBase]。

 [AddInBase]
    public abstract class ImageProcessorAddInView
    {
        public abstract byte[] ProcessImageBytes(byte[] pixels);

        public abstract void Initialize(HostObject hostObj);
    }

    public abstract class HostObject
    {
        public abstract void ReportProgress(int progressPercent);
    }

之后在HostSideAdapter实现抽象类。

注意HostSideAdapter继承HostView的抽象类,在构造函数里需设置ContractHandle插件生存周期,ContractHandle不能为readonly。

  [HostAdapter]
    public class ImageProcessorContractToViewHostAdapter : HostView.ImageProcessorHostView
    {
        private Contract.IImageProcessorContract contract;
        private ContractHandle contractHandle;

        public ImageProcessorContractToViewHostAdapter(Contract.IImageProcessorContract contract)
        {            
            this.contract = contract;
            contractHandle = new ContractHandle(contract);
        }              

        public override byte[] ProcessImageBytes(byte[] pixels)
        {
            return contract.ProcessImageBytes(pixels);
        }

        public override void Initialize(HostView.HostObject host)
        {            
            HostObjectViewToContractHostAdapter hostAdapter = new HostObjectViewToContractHostAdapter(host);
            contract.Initialize(hostAdapter);
        }
    }

    public class HostObjectViewToContractHostAdapter : ContractBase, Contract.IHostObjectContract
    {
        private HostView.HostObject view;

        public HostObjectViewToContractHostAdapter(HostView.HostObject view)
        {
            this.view = view;
        }

        public void ReportProgress(int progressPercent)
        {
            view.ReportProgress(progressPercent);
        }        
    }

在AddInSideAdapter实现Contract接口,基本和HostSideAdapter类似,只是继承的类不同。

[AddInAdapter]
    public class ImageProcessorViewToContractAdapter : ContractBase, Contract.IImageProcessorContract
    {
        private AddInView.ImageProcessorAddInView view;

        public ImageProcessorViewToContractAdapter(AddInView.ImageProcessorAddInView view)
        {
            this.view = view;
        }

        public byte[] ProcessImageBytes(byte[] pixels)
        {
            return view.ProcessImageBytes(pixels);
        }

        public void Initialize(Contract.IHostObjectContract hostObj)
        {            
            view.Initialize(new HostObjectContractToViewAddInAdapter(hostObj));            
        }
    }

    public class HostObjectContractToViewAddInAdapter : AddInView.HostObject
    {
        private Contract.IHostObjectContract contract;
        private ContractHandle handle;

        public HostObjectContractToViewAddInAdapter(Contract.IHostObjectContract contract)
        {
            this.contract = contract;
            this.handle = new ContractHandle(contract);            
        }
                
        public override void ReportProgress(int progressPercent)
        {
            contract.ReportProgress(progressPercent);
        }
    }

宿主项目中需要实现HostView里HostObject抽象类。

 private class AutomationHost : HostView.HostObject
        {
            private ProgressBar progressBar;
            public AutomationHost(ProgressBar progressBar)
            {
                this.progressBar = progressBar;
            }
            public override void ReportProgress(int progressPercent)
            {
                // Update the UI on the UI thread.
                progressBar.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                    (ThreadStart)delegate()
                {
                    progressBar.Value = progressPercent;
                }
                );                
            }
        }

然后是在宿主项目里激活插件,并初始化AutomationHost。

string path = Environment.CurrentDirectory;            
AddInStore.Update(path);//更新目录中Addins目录里的插件
IList<AddInToken> tokens = AddInStore.FindAddIns(typeof(HostView.ImageProcessorHostView), path);//查找全部插件
lstAddIns.ItemsSource = tokens;//插件可视化
AddInToken token = (AddInToken)lstAddIns.SelectedItem;//选择插件
AddInProcess addInProcess = new AddInProcess();//创建插件进程
addInProcess.Start();//激活插件进程
addin = token.Activate<HostView.ImageProcessorHostView>(addInProcess,AddInSecurityLevel.Internet);//激活插件
//如果只是想隔离程序域,就无需创建AddInProcess,激活插件如下
// HostView.ImageProcessorHostView addin = token.Activate<HostView.ImageProcessorHostView>(AddInSecurityLevel.Host);
automationHost = new AutomationHost(progressBar);//创建AutomationHost类
addin.Initialize(automationHost);//初始化automationHost

 插件项目中实现AddInView中的抽象类。

[AddIn("Negative Image Processor", Version = "1.0", Publisher = "Imaginomics",Description = "")]
    public class NegativeImageProcessor : AddInView.ImageProcessorAddInView 
    {
        public override byte[] ProcessImageBytes(byte[] pixels)
        {
            int iteration = pixels.Length / 100;         
            for (int i = 0; i < pixels.Length - 2; i++)
            {
                pixels[i] = (byte)(255 - pixels[i]);
                pixels[i + 1] = (byte)(255 - pixels[i + 1]);
                pixels[i + 2] = (byte)(255 - pixels[i + 2]);
                if (i % iteration == 0)
                {
                    host?.ReportProgress(i / iteration);
                }
            }
            return pixels;
        }

        private AddInView.HostObject host;
        public override void Initialize(AddInView.HostObject hostObj)
        {
            host = hostObj;
        }

 这时宿主可以把数据传递给插件程序,插件程序中ProcessImageBytes处理数据然后通过host?.ReportProgress(i / iteration);向宿主传递消息。

这里有提供样例程序。

项目附件.7z

5. MAF框架常见问题

5.1手动关闭插件

AddInController addInController = AddInController.GetAddInController(addIn);
addInController.Shutdown();

此方法适应于非应用隔离的手动关闭。对于应用隔离式插件,用此方法会抛出异常。

如上面样例就是应用隔离的插件,可以根据进程id直接关闭进程。

public void ProcessClose()
        {
            try
            {
                if (process != null)
                {
                    Process processes = Process.GetProcessById(addInProcess.ProcessId);
                    if (processes?.Id > 0)
                    {
                        processes.Close();
                    }
                }
            }
            catch (Exception)
            {
            }5.2 插件异常

System.Runtime.Remoting.RemotingException: 从 IPC 端口读取时失败: 管道已结束。这是插件最常见的异常,因为插件抛出异常而使得插件程序关闭。

如果是插件调用非托管代码,而产生的异常,可以查Windows应用程序日志来确定异常。其余能捕获的异常尽量捕获保存到日志,方便查看。

5.3 双向通信

实际应用过程中,往往是通过委托来将宿主相关函数暴露給一个类,然后通过在宿主程序初始化后。在插件中实例化后就可以直接调用宿主的相关函数,反之同理。

这里是通过委托暴露宿主的一个函数。

public delegate void UpdateCallBack(string message, bool isclose, int leve);
    public class VideoHost : HostAddInView
    {
        public event UpdateCallBack Updatecallback;
        public override void ProcessVideoCallBack(string message, bool isclose, int leve)
        {
            Updatecallback?.Invoke(message, isclose, leve);
        }
    }

 在插件程序中实例化后调用。

private HostAddInView hostAddInView;
        public override void Initialize(HostAddInView hostAddInView)
        {
            this.hostAddInView = hostAddInView;
        }
        private void ErrorCallback(string message, bool isclose, int leve)
        {
            hostAddInView?.ProcessVideoCallBack(message, isclose, leve);
        }

6. MAF深入理解

 MAF本质是实现IpcChannel通信,在一个期刊中有作者抛弃MAF固定结构自己实现IpcChannel,因为代码很复杂,就不在此详细阐述。

如果要实现应用域隔离,自己实现IpcChannel,MAF中的应用域隔离实现也是非常好的参考资料。

MAF的7层结构主要是实现从插件的宿主函数转换,例如可以在将插件程序的界面放入主界面中渲染,做出像浏览器一样的开一个界面就是一个进程。将插件中的组件在AddInSideAdapter中转换为Stream然后在HostSideAdapter中将Stream实例化为组件。而HostView和AddInView实际上是提供两个转换接口,Contract是定义传输接口。

另外如果传输插件向数组传递图像数据,最后是转换成byte[],或者使用共享内存。 

更多推荐

PT@Bernoulli概型@古典概型之伯努利概型

文章目录abstract伯努利概型伯努利试验n重伯努利试验例样本空间样本空间的重要划分成功k次的n重Bernoulli试验例例例abstractBernoulli概型是结合独立事件和n重Bernoulli试验概念的古典概型伯努利概型Bernoulli概型是基于bernoulli试验的一类古典概型这类概型的等可能性体现在

定时任务框架-xxljob

1.定时任务spring传统的定时任务@Scheduled,但是这样存在这一些问题:做集群任务的重复执行问题cron表达式定义在代码之中,修改不方便定时任务失败了,无法重试也没有统计如果任务量过大,不能有效的分片执行解决这些问题的方案为:xxl-job分布式任务调度框架2.分布式任务调度2.1什么是分布式任务调度当前软

R语言贝叶斯非参数模型:密度估计、非参数化随机效应META分析心肌梗死数据...

全文链接:http://tecdat.cn/?p=23785最近,我们使用贝叶斯非参数(BNP)混合模型进行马尔科夫链蒙特卡洛(MCMC)推断(点击文末“阅读原文”获取完整代码数据)。概述相关视频在这篇文章中,我们通过展示如何使用具有不同内核的非参数混合模型进行密度估计。在后面的文章中,我们将采用参数化的广义线性混合模

idea快捷键

目录前言一.Ctrl相关二.Alt相关三.Shift相关四.Ctrl+Alt相关五.Ctrl+Shift相关六.Alt+Shift相关七.其他汇总前言IDEA中提供了很多快捷键,点击File-->Settings-->keymap便可进入看到IDEA提供的快捷键。我们也可以搜索和自定义所有快捷键,下面给出的是IDEA中

uniapp瀑布流布局写法

首先我们要清楚瀑布流是什么?瀑布流布局(WaterfallFlowLayout),也称为瀑布流式布局,是一种常见的网页或移动应用布局方式,特点是元素以不规则的方式排列,就像瀑布中的流水一样,每个元素的高度可以不同。主要特点和优点包括:不规则的排列:瀑布流布局允许元素以不同的高度和宽度排列,因此适用于展示不同尺寸和形状的

网络安全(黑客)自学

想自学网络安全(黑客技术)首先你得了解什么是网络安全!什么是黑客网络安全可以基于攻击和防御视角来分类,我们经常听到的“红队”、“渗透测试”等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。无论网络、Web、移动、桌面、云等哪个领域,都有攻与防两面性,例如Web安全技术,既有Web渗透,也有Web

坐标休斯顿,TDengine 受邀参与第九届石油天然气数字化大会

美国中部时间9月14日至15日,第九届石油天然气数字化大会在美国德克萨斯州-休斯顿-希尔顿美洲酒店举办。本次大会汇聚了数百名全球石油天然气技术高管及众多极具创新性的数据技术方案商,组织了上百场硬核演讲,技术专家与行业从业者共聚一堂,共同探讨石油天然气领域的创新技术和解决方案,全面了解整个行业的数字化潜力。TDengin

心理健康数据集:mental_health_chatbot_dataset

一.数据集描述1.数据集摘要该数据集包含与心理健康相关的问题和答案的对话对,以单一文本形式呈现。数据集是从流行的医疗博客(如WebMD、MayoClinic和HealthLine)、在线常见问题等来源精选而来的。所有问题和答案都经过匿名化处理,以删除任何个人身份信息(PII),并经过预处理以删除任何不必要的字符。2.语

RabbitMQ 消息应答

每日一句物是人非事事休,欲语泪先流。概述为了保证消息在发送过程中不丢失,RabbitMQ引入了消息应答机制,消费者在接收到消息并且处理该消息后,告诉RabbitMQ它已经处理了,RabbitMQ可以把消息删除了。自动应答消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权衡。因为这种模式有两

【评论内容关键词提取】多种主流提取算法与大模型测试

文章目录1.写在前面2.TextRank关键词提取算法3.TFIDF算法4.jionlp算法5.sklearn算法6.Rake算法7.hanlp情感分析8.大语言模型1.写在前面做过舆情项目或文本内容情感分析的大家都知道,我们要从大量的文本内容中提取核心短语或者关键词!最近我们的爬虫项目中正好遇到了这么一个需求,我们收

服务器环境的关键组成部分

服务器环境是指服务器硬件和软件组成的整体环境,包括操作系统、网络配置、数据库、Web服务器软件、应用程序等。它提供了服务器运行和支持所需的基本条件和组件。以下是服务器环境中的一些关键组成部分:操作系统:服务器环境通常基于某种操作系统,如Linux、WindowsServer等。操作系统提供了服务器的基本功能和管理能力,

热文推荐