CCR 解锁





5.00/5 (5投票s)
利用 CCR 来管理和执行插件。
引言
最近,我不得不解决一个典型问题。构建一个“东西”,使其能够随着时间的推移而增长,易于未来开发者维护,并有效地利用 CPU 资源。嗯,话虽如此,我认为我们编写的大部分内容都应该考虑这些因素。然而,并非所有挑战都相同,因此在这种情况下,我再次挖掘了 CCR;它过去从未让我失望,为什么现在会呢?
我写这篇文章的前提是,“中级”开发者将阅读本文,因此,我省略了很多代码描述,仅仅是因为描述每个部分对我来说是不必要的。您应该对谓词(Predicates)和 Lambda 有相当的了解,才能理解本文示例的某些部分。如果您是那种通过示例学习的人,那么您应该没问题。
背景
项目的目标是创建一个类似 WISE 进程的东西。必须有一个“核心”,能够管理新的代码部分并适当地分配它们,而无需重新编译核心。因此,通过利用“插件”技术并通过 CCR 协调它们的执行,可以很容易地实现这一点。
以下文章从基本形式上处理了概念部分,从原始项目中剥离出来,以便演示一个有效的解决方案。
Using the Code
我想从回顾解决方案的各个部分开始。
为了创建插件,我们需要两样东西。首先也是最重要的,是我们的接口,这样我们就可以识别在我们的Plugin目录中找到的已加载的 DLL。其次,我们将创建一些事件参数,以帮助我们确定我们的插件是失败还是成功。
请注意,在 IPlugin
接口中,我们必须实现一个事件 (ProcessComplete
),用于“回调”到某个地方,告知我们插件已退出;这对本次演示来说不是必需的;然而,在生产环境中,我们可能希望为应用程序中的事件保留审计跟踪。
我们还有一个主方法“BeginProcess
”。当由于新线程的可用性而调用监视器时,这是我们的“PluginMonitor
”将调用的方法。您可能不应该将其视为初始化方法或构造函数,因为在线程附加后将立即监视插件的“状态”。
最终方法“Done
”应该用于处理任何对象,并且,如果存在,则调用连接到 ProcessComplete
的方法。如前所述,这并非必需,但可能是个好习惯。
现在,让我们快速回顾一下将完成所有工作的主要类。
PluginProvider
:我们需要一个对象来为我们收集所有插件。它非常简单,并且可以提取、修改并用于您可能想要将此技术集成到的任何内容中。它假定我们有一个名为“Plugins”的文件夹,我们将把所有继承了IPlugin
接口的 DLL 放入其中。PluginMonitor
:我们还需要一个包装插件的东西,以便在分配的线程上对其进行监视。我们不想将其作为PluginProcessor
的一部分来做,因为我们希望根据其职责来分离对象。PluginProcessor
:这是我们想要放入所有 CCR 逻辑的地方;它可能与我上一篇文章中创建的用于线程顺序日志记录的解决方案非常相似。我希望在此示例中使用最简单的实现来帮助您入门,并且我可能会在将来重新审视它以进一步改进此代码。请注意,我们还有一个事件“AddProcess
”。我包含了这个事件,以帮助我们跟踪哪些插件正在被执行,仅仅是为了帮助我们理解和查看运行时发生的情况。
快速查看通用接口会发现一些相当标准的东西。稍后,我将创建一个测试插件,我们可以用它来测试“插件管理器”。我们只需将同一个插件的多个版本复制并粘贴到plugins目录中。
IPlugin
using System;
namespace Common
{
public interface IPlugin
{
event EventHandler ProcessComplete;
string PluginName { get; set; }
string PluginDescription { get; set; }
int PercentComplete { get; set; }
void BeginProcess();
void Done(PluginEventArgs evenArgs);
}
public class PluginEventArgs : EventArgs
{
public bool Success { get; set; }
public Exception Error { get; set; }
}
}
现在,让我们看看 PluginProvider
。这个对象只是加载 Plugins 目录中的所有适当的 DLL,并返回一个 IPlugin
类型的 List
。
PluginProvider
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Common;
namespace Director
{
public static class PluginProvider
{
public static List<IPlugin> GetPlugins()
{
return FindPlugIns(LoadPlugInAssemblies());
}
private static List<Assembly> LoadPlugInAssemblies()
{
var dInfo = new DirectoryInfo(Path.Combine(
Environment.CurrentDirectory, "Plugins"));
var files = dInfo.GetFiles("*.dll");
var plugInAssemblyList = new List<Assembly>();
foreach (var file in files)
plugInAssemblyList.Add(Assembly.LoadFile(file.FullName));
return plugInAssemblyList;
}
private static List<IPlugin> FindPlugIns(IEnumerable<Assembly> assemblies)
{
var availableTypes = new List<Type>();
foreach (var currentAssembly in assemblies)
availableTypes.AddRange(currentAssembly.GetTypes());
var pluginList = availableTypes.FindAll(t =>
new List<Type>(t.GetInterfaces()).Contains(typeof(IPlugin)));
return pluginList.ConvertAll(t => Activator.CreateInstance(t) as IPlugin);
}
}
}
对于不熟悉 Lambda 的人,这里有一个如何利用它的示例。在某些方面,它会降低可读性,但肯定会减少您的打字量!:)
首先,我们使用“FindAll
”来收集所有“IPlugin
”类型,然后我们使用 ConvertAll
将程序集对象返回为 IPlugin
类型。
var pluginList = availableTypes.FindAll(t =>
new List<Type>(t.GetInterfaces()).Contains(typeof(IPlugin)));
return pluginList.ConvertAll(t => Activator.CreateInstance(t) as IPlugin);
最后,核心“Director”模块的最后一块是 PluginProcessor
。这一部分严格处理 CCR。请参考我上一篇文章关于 CCR 的内容,以帮助理解以下对象。
请注意,在此示例中,我没有使用“后台线程”(Dispatcher
对象);因此,您需要调用 Dispose
方法才能正确退出应用程序。
如果您希望跟踪已启动的插件,请确保连接“AddProcess
”。
PluginProcessor
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Ccr.Core;
namespace Director
{
public class PluginProcessor : IDisposable
{
//Create Dispatcher
private static readonly Dispatcher MainDispatcher =
new Dispatcher(2, ThreadPriority.Normal, false, "ProcessPool");
//Create Dispatch Queue
private static DispatcherQueue _mainDispatcherQueue;
//Message Port
private static readonly Port<PluginMonitor> MainProcessPort =
new Port<PluginMonitor>();
//Dispose flag
private bool _disposed;
//Custom Events
public delegate void ProcessDelegate(PluginMonitor processMonitor);
public event ProcessDelegate AddProcess;
public PluginProcessor(IEnumerable<PluginMonitor> processes)
{
//Queue all jobs
foreach (var process in processes)
MainProcessPort.Post(process);
_mainDispatcherQueue = new DispatcherQueue(
"MainDispatcherQueue", MainDispatcher);
Arbiter.Activate(_mainDispatcherQueue,
Arbiter.Receive(true, MainProcessPort, StartProcess));
}
private void StartProcess(PluginMonitor pluginMonitor)
{
if (AddProcess != null)
AddProcess(pluginMonitor);
pluginMonitor.BeginProcess();
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
MainDispatcher.Dispose();
_mainDispatcherQueue.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
现在,我们只需要创建一个插件。下面的代码基本上不做任何重要的事情,但它会在不同时间间隔模拟输出和进度。我感到有义务解释其中一些代码,因为有些部分似乎有点傻,但它们确实有真正的原因。在此插件的开头,您会注意到类似这样的内容
_pluginID = Convert.ToUInt16(Guid.NewGuid().ToString()[0]);
_random = new Random(_pluginID);
之所以存在这一点,是为了帮助为随机数生成器创建一个真正新的“种子”值,因为您最终会有多个插件同时完成。虽然不是什么大事,但看到插件的“存活”时间变化的结果还是不错的。以下代码是 IPlugin
继承类的完整实现。我们只需要将编译后的 DLL 从 bin 复制到主应用程序的Plugins目录中,然后再执行它。如我所述,它实际上什么也没做,它只是创建一个变量随机长度的计时器来模拟完整的处理时间。它更改描述状态字段来模拟正在发生的事情。都是为了展示。
using System;
using System.Threading;
using Common;
namespace MyPlugin
{
class TestPlugin : IPlugin
{
public event EventHandler ProcessComplete;
private readonly int _pluginID;
private readonly Random _random;
private int _completed;
private Timer _tmr;
public TestPlugin()
{
_pluginID = Convert.ToUInt16(Guid.NewGuid().ToString()[0]);
_random = new Random(_pluginID);
}
public void BeginProcess()
{
_tmr = new Timer(Counter, null, 0, _random.Next(5, 20));
}
public string PluginName
{
get { return String.Format("Plugin ID : {0}", _pluginID); }
set{}
}
public int PercentComplete
{
get { return _completed; }
set {}
}
public string PluginDescription { get; set; }
private void Counter(object sender)
{
_completed += 1;
switch (_completed)
{
case 1:
PluginDescription = "Initializing plugin.";
break;
case 20:
PluginDescription = "Gathering plugin information.";
break;
case 40:
PluginDescription = "Examining registry";
break;
case 60:
PluginDescription = "Examining files.";
break;
case 80:
PluginDescription = "Compiling restore point.";
break;
case 90:
PluginDescription = "Cleaning up temporary files.";
break;
}
if (_completed < 100) return;
_tmr.Dispose();
Done(new PluginEventArgs { Error = null, Success = true });
}
public void Done(PluginEventArgs evenArgs)
{
if (ProcessComplete != null)
ProcessComplete(this, evenArgs);
}
}
}
最后,是应用程序核心的实现。在这种情况下,我创建了一个小型控制台应用程序。首先,我们调用 Provider 来获取符合条件的插件对象列表。我们需要通过将每个“插件”传递给监视器来创建一个 <PluginMonitor>
的 List
。
var processMonitors = new List<PluginMonitor>();
var plugins = PluginProvider.GetPlugins();
foreach (var p in plugins)
processMonitors.Add(new PluginMonitor(p));
完成后,我们需要将所有这些 ProcessMonitor
s 传递给 PluginProcessor
;它将调用 CCR 并处理所有线程。我们还希望连接“AddProcess
”,以便我们可以监视每个插件的执行。
_processor = new PluginProcessor(processMonitors);
_processor.AddProcess += PluginMonitorAddProcess;
这是实现的完整列表
using System;
using System.Collections.Generic;
using Director;
namespace Interface
{
class Program
{
static void Main(string[] args)
{
new MainApp();
}
}
public class MainApp
{
private readonly PluginProcessor _processor;
public MainApp()
{
var processMonitors = new List<PluginMonitor>();
var plugins = PluginProvider.GetPlugins();
foreach (var p in plugins)
processMonitors.Add(new PluginMonitor(p));
_processor = new PluginProcessor(processMonitors);
_processor.AddProcess += PluginMonitorAddProcess;
}
static void PluginMonitorAddProcess(PluginMonitor pluginMonitor)
{
Console.WriteLine(string.Format("{0} plugin started.",
pluginMonitor.PluginName));
}
}
}
运行代码示例
编译应用程序后,您需要将 TestPlugin 中的 DLL 复制到主 EXE 所在目录的名为 Plugins 的文件夹中。如果文件夹不存在,请创建它。
您应该会看到类似这样的内容
您需要查看代码以确定输出的含义。总的来说,它显示了每个插件当前的“状态和完成百分比”等信息。图形化显示,这个过程看起来要好得多,但希望您可以将其提升到更高的水平。
我希望这篇文章能帮助您产生新的创新,或激发您的创造力来改进过去、现在和未来的代码项目!
关注点
您可能想尝试调整 PluginProcessor
中的“Dispatcher
”对象,以更好地理解进程的分配。尝试更新 IPlugin
接口以包含依赖项和执行排序等内容。
祝您 CCR 愉快!