如何在多个脚本的批处理中使用Roslyn C#脚本?
我正在编写多线程解决方案,用于将不同来源的数据传输到中央数据库。解决方案一般分为两部分:如何在多个脚本的批处理中使用Roslyn C#脚本?,c#,scripting,roslyn,C#,Scripting,Roslyn,我正在编写多线程解决方案,用于将不同来源的数据传输到中央数据库。解决方案一般分为两部分: 单线程导入引擎 在线程中调用导入引擎的多线程客户端 为了最小化定制开发,我使用了Roslyn脚本。此功能在导入引擎项目中的Nuget Package manager中启用。 每次导入都定义为输入表(包含输入字段集合)到目标表(同样包含目标字段集合)的转换 这里使用脚本引擎来允许在输入和输出之间进行自定义转换。对于每个输入/输出对,都有带有自定义脚本的文本字段。以下是用于脚本初始化的简化代码: //Insta
//Instance of class passed to script engine
_ScriptHost = new ScriptHost_Import();
if (Script != "") //Here we have script fetched from DB as text
{
try
{
//We are creating script object …
ScriptObject = CSharpScript.Create<string>(Script, globalsType: typeof(ScriptHost_Import));
//… and we are compiling it upfront to save time since this might be invoked multiple times.
ScriptObject.Compile();
IsScriptCompiled = true;
}
catch
{
IsScriptCompiled = false;
}
}
//传递给脚本引擎的类的实例
_ScriptHost=新ScriptHost_Import();
if(Script!=“”)//这里我们将脚本作为文本从数据库中获取
{
尝试
{
//我们正在创建脚本对象…
ScriptObject=CSharpScript.Create(脚本,globalType:typeof(ScriptHost_Import));
//…我们正在预先编译它以节省时间,因为它可能会被多次调用。
ScriptObject.Compile();
IsScriptCompiled=true;
}
抓住
{
IsScriptCompiled=false;
}
}
稍后,我们将使用以下命令调用此脚本:
async Task<string> RunScript()
{
return (await ScriptObject.RunAsync(_ScriptHost)).ReturnValue.ToString();
}
异步任务运行脚本()
{
return(wait ScriptObject.RunAsync(_ScriptHost)).ReturnValue.ToString();
}
所以,在导入定义初始化之后,我们可能有任意数量的输入/输出对描述以及脚本对象,在定义脚本的地方,内存足迹每对增加大约50MB。
在将目标行存储到数据库之前,类似的使用模式应用于目标行的验证(每个字段可能有几个脚本用于检查数据的有效性)
总而言之,具有适度转换/验证脚本的典型内存占用是每个线程200 MB。如果我们需要调用多个线程,内存使用率将非常高,99%将用于编写脚本。
若导入引擎被封装在基于WCF的中间层(我这么做了),我们很快就会遇到“内存不足”的问题
显而易见的解决方案是使用一个脚本实例,根据需要(输入/输出转换、验证或其他)以某种方式将代码执行分派到脚本中的特定函数。也就是说,我们将使用脚本ID代替每个字段的脚本文本,该脚本ID将作为全局参数传递给脚本引擎。在脚本的某个地方,我们需要切换到将执行并返回适当值的代码的特定部分
这种解决方案的好处应该是更好地使用内存。缺点:脚本维护从使用它的特定点删除
在实施此更改之前,我想听听关于此解决方案的意见和对不同方法的建议。看起来,为任务使用脚本可能是一种浪费性的过度使用-您使用了许多应用程序层,并且内存已满 其他解决办法:
- 如何与数据库接口?您可以根据需要操作查询本身,而不是为此编写整个脚本
- 使用泛型怎么样?有足够的T以满足您的需求:
public class ImportEngine
- 使用(非常类似于使用泛型)
RunAsync
中进行工作。以下是一个例子:
而不是简单地(脚本字符串):
您可以这样做(IHaveWork是应用程序中定义的一个接口,只有一种方法Work
):
通过这种方式,您只能在短时间内调用重载RunAsync,它返回一个可在应用程序内重复使用的worker(当然,您可以通过向Work方法添加参数和从应用程序继承逻辑等方式对此进行扩展…)
该模式还打破了应用程序和脚本之间的隔离,因此您可以轻松地从脚本中提供和获取数据
编辑
一些快速基准:
此代码:
static void Main(string[] args)
{
Console.WriteLine("Compiling");
string code = "System.Threading.Thread.SpinWait(100000000); System.Console.WriteLine(\" Script end\");";
List<Script<object>> scripts = Enumerable.Range(0, 50).Select(num =>
CSharpScript.Create(code, ScriptOptions.Default.WithReferences(typeof(Control).Assembly))).ToList();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); // for fair-play
for (int i = 0; i < 10; i++)
Task.WaitAll(scripts.Select(script => script.RunAsync()).ToArray());
}
在这个脚本中,我稍微简化了我提出的返回Action
对象的解决方案,但我认为对性能的影响很小(但在实际实现中,我真的认为您应该使用自己的接口使其灵活)
当脚本运行时,您可以看到内存急剧增加到~240MB,但在我调用垃圾收集器(出于演示目的,我在前面的代码中也这样做了)之后,内存使用量下降到~30MB。它也更快。我不确定在创建问题时是否存在这种情况,但有一种非常类似的方法,比如说,如何在不增加程序内存的情况下多次运行脚本。您需要使用CreateDelegate方法,该方法将完全执行预期的操作 为了方便起见,我将在这里发布:
var script = CSharpScript.Create<int>("X*Y", globalsType: typeof(Globals));
ScriptRunner<int> runner = script.CreateDelegate();
for (int i = 0; i < 10; i++)
{
Console.WriteLine(await runner(new Globals { X = i, Y = i }));
}
var script=CSharpScript.Create(“X*Y”,globalsType:typeof(Globals));
ScriptRunner runner=script.CreateDelegate();
对于(int i=0;i<10;i++)
{
WriteLine(wait runner(新全局变量{X=i,Y=i}));
}
最初它需要一些内存,但将runner保留在一些全局列表中,稍后快速调用它。导入引擎是更大工具集的一小部分。通常,脚本的使用大大增加了这个解决方案的难度。因此,就目前而言,使用Roslyn实现的这种脚本是完美的。就内存消耗而言,我已经注意到了
static void Main(string[] args)
{
Console.WriteLine("Compiling");
string code = "System.Threading.Thread.SpinWait(100000000); System.Console.WriteLine(\" Script end\");";
List<Script<object>> scripts = Enumerable.Range(0, 50).Select(num =>
CSharpScript.Create(code, ScriptOptions.Default.WithReferences(typeof(Control).Assembly))).ToList();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); // for fair-play
for (int i = 0; i < 10; i++)
Task.WaitAll(scripts.Select(script => script.RunAsync()).ToArray());
}
static void Main(string[] args)
{
Console.WriteLine("Compiling");
string code = "return () => { System.Threading.Thread.SpinWait(100000000); System.Console.WriteLine(\" Script end\"); };";
List<Action> scripts = Enumerable.Range(0, 50).Select(async num =>
await CSharpScript.EvaluateAsync<Action>(code, ScriptOptions.Default.WithReferences(typeof(Control).Assembly))).Select(t => t.Result).ToList();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
for (int i = 0; i < 10; i++)
Task.WaitAll(scripts.Select(script => Task.Run(script)).ToArray());
}
var script = CSharpScript.Create<int>("X*Y", globalsType: typeof(Globals));
ScriptRunner<int> runner = script.CreateDelegate();
for (int i = 0; i < 10; i++)
{
Console.WriteLine(await runner(new Globals { X = i, Y = i }));
}